/*
 * This file is part of OpenModelica.
 *
 * Copyright (c) 1998-2014, Open Source Modelica Consortium (OSMC),
 * c/o Linköpings universitet, Department of Computer and Information Science,
 * SE-58183 Linköping, Sweden.
 *
 * All rights reserved.
 *
 * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF GPL VERSION 3 LICENSE OR
 * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.2.
 * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES
 * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GPL VERSION 3,
 * ACCORDING TO RECIPIENTS CHOICE.
 *
 * The OpenModelica software and the Open Source Modelica
 * Consortium (OSMC) Public License (OSMC-PL) are obtained
 * from OSMC, either from the above address,
 * from the URLs: http://www.ida.liu.se/projects/OpenModelica or
 * http://www.openmodelica.org, and in the OpenModelica distribution.
 * GNU version 3 is obtained from: http://www.gnu.org/copyleft/gpl.html.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without
 * even the implied warranty of  MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH
 * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL.
 *
 * See the full OSMC Public License conditions for more details.
 *
 */

encapsulated package CevalScript
" file:        CevalScript.mo
  package:     CevalScript
  description: Constant propagation of expressions


  This module handles scripting.

  Input:
    Env: Environment with bindings
    Exp: Expression to evaluate
    Bool flag determines whether the current instantiation is implicit
    InteractiveSymbolTable is optional, and used in interactive mode, e.g. from OMShell

  Output:
    Value: The evaluated value
    InteractiveSymbolTable: Modified symbol table
    Subscript list : Evaluates subscripts and generates constant expressions."

// public imports
import Absyn;
import AbsynUtil;
import Ceval;
import CevalScriptOMSimulator;
import DAE;
import ErrorTypes;
import FCore;
import GlobalScript;
import Interactive;
import SimCode;
import Values;

// protected imports
protected
import Autoconf;
import BaseHashSet;
import Builtin;
import CevalFunction;
import CevalScriptBackend;
import ClassInf;
import ClassLoader;
import CodegenCFunctions;
import ComponentReference;
import Config;
import Corba;
import DAEUtil;
import Debug;
import Dump;
import DynLoad;
import Error;
import ErrorExt;
import ExecStat.{execStat,execStatReset};
import Expression;
import ExpressionDump;
import FBuiltin;
import FGraph;
import Flags;
import FlagsUtil;
import FNode;
import GCExt;
import GenerateAPIFunctionsTpl;
import Global;
import Graph;
import HashSetString;
import Inst;
import InstFunction;
import InteractiveUtil;
import List;
import Lookup;
import Mod;
import PackageManagement;
import Parser;
import Print;
import SCode;
import SCodeDump;
import SCodeUtil;
import SemanticVersion;
import Settings;
import SimCodeFunction;
import StackOverflow;
import Static;
import StringUtil;
import SymbolTable;
import System;
import Tpl;
import Types;
import UnorderedMap;
import Unparsing;
import Util;
import ValuesUtil;
import MetaModelica.Dangerous.listReverseInPlace;

public

function ceval "
  This is a wrapper funtion to Ceval.ceval. The purpose of this
  function is to concetrate all the calls to Ceval.ceval made from
  the Script files. This will simplify the separation of the scripting
  environment from the FrontEnd"
  input FCore.Cache inCache;
  input FCore.Graph inEnv;
  input DAE.Exp inExp;
  input Boolean inBoolean "impl";
  input Absyn.Msg inMsg;
  input Integer numIter;
  output FCore.Cache outCache;
  output Values.Value outValue;

  partial function ReductionOperator
    input Values.Value v1;
    input Values.Value v2;
    output Values.Value res;
  end ReductionOperator;
algorithm
  (outCache,outValue):=
  matchcontinue (inCache,inEnv,inExp,inBoolean,inMsg,numIter)
    local
      Boolean impl;
      FCore.Graph env;
      Absyn.Msg msg;
      list<Values.Value> vallst;
      list<DAE.Exp> expl;
      Values.Value newval,value;
      DAE.Exp e;
      Absyn.Path funcpath;
      FCore.Cache cache;

    // adrpo: TODO! this needs more work as if we don't have a symtab we run into unloading of dlls problem
    case (cache,env,(e as DAE.CALL(path = funcpath,expLst = expl)),impl,msg,_)
      equation
        // do not handle Connection.isRoot here!
        false = stringEq("Connection.isRoot", AbsynUtil.pathString(funcpath));
        // do not roll back errors generated by evaluating the arguments
        (cache,vallst) = Ceval.cevalList(cache,env, expl, impl, msg, numIter);

        (cache,newval)= cevalCallFunction(cache, env, e, vallst, impl, msg, numIter+1);
      then
        (cache,newval);

    // Try Interactive functions last
    case (cache,env,(e as DAE.CALL()),(true),msg,_)
      equation
        (cache,value) = cevalInteractiveFunctions(cache, env, e, msg, numIter+1);
      then
        (cache,value);
    case (cache,env,e,impl,msg,_)
      equation
        (cache,value) = Ceval.ceval(cache,env,e,impl,msg,numIter+1);
      then
         (cache,value);
  end matchcontinue;
end ceval;

public function isCompleteFunction
"a function is complete if is:
 - not partial
 - not replaceable (without redeclare)
 - replaceable and called functions are not partial or not replaceable (without redeclare)"
  input FCore.Cache inCache;
  input FCore.Graph inEnv;
  input Absyn.Path inFuncPath;
  output Boolean isComplete;
algorithm
 isComplete := matchcontinue(inCache, inEnv, inFuncPath)
   local
     FCore.Cache cache;
     FCore.Graph env;
     Absyn.Path fpath;

   // external functions are complete :)
   case (cache, env, fpath)
     equation
       (_, SCode.CLASS(classDef = SCode.PARTS(externalDecl = SOME(_))), _) = Lookup.lookupClass(cache, env, fpath);
     then
       true;

   // if is partial instantiation no function evaluation/generation
   case (_, _, _)
     equation
       true = System.getPartialInstantiation();
     then
       false;

   // partial functions are not complete!
   case (cache, env, fpath)
     equation
       (_, SCode.CLASS(partialPrefix = SCode.PARTIAL()), _) = Lookup.lookupClass(cache, env, fpath);
     then
       false;

   else true;

  end matchcontinue;
end isCompleteFunction;

public function compileModel "Compiles a model given a file-prefix, helper function to buildModel."
  input String fileprefix;
  input list<String> libs;
  input String workingDir = "";
  input list<String> makeVars = {};
protected
  String omhome = Settings.getInstallationDirectoryPath(),omhome_1 = System.stringReplace(omhome, "\"", "");
  String pd = Autoconf.pathDelimiter;
  String cdWorkingDir,setMakeVars,libsfilename,libs_str,s_call,filename,winCompileMode,workDir = (if stringEq(workingDir, "") then "" else workingDir + pd), linkType = "dynamic";
  String fileDLL = workDir + fileprefix + Autoconf.dllExt,
         fileEXE = workDir + fileprefix + Autoconf.exeExt,
         fileLOG = workDir + fileprefix + ".log";
  Integer numParallel,res;
  Boolean isWindows = Autoconf.os == "Windows_NT";
  list<String> makeVarsNoBinding;
algorithm
  libsfilename := workDir + fileprefix + ".libs";
  libs_str := stringDelimitList(libs, " ");
  makeVarsNoBinding := makeVars; // OMC is stupid and wants to constant evaluate inputs with bindings for iterator variables...

  System.writeFile(libsfilename, libs_str);
  if isWindows then
    // We only need to set OPENMODELICAHOME on Windows, and set doesn't work in bash shells anyway
    // adrpo: 2010-10-05:
    //        whatever you do, DO NOT add a space before the && otherwise
    //        OPENMODELICAHOME that we set will contain a SPACE at the end!
    //        set OPENMODELICAHOME=DIR && actually adds the space between the DIR and &&
    //        to the environment variable! Don't ask me why, ask Microsoft.
    omhome := "set OPENMODELICAHOME=" + System.stringReplace(omhome_1, "/", "\\") + "&& ";
    setMakeVars := sum("set "+var+"&& " for var in makeVarsNoBinding);
    cdWorkingDir := if stringEmpty(workingDir) then "" else ("cd \"" + workingDir + "\"&& ");
    winCompileMode := if Testsuite.isRunning() then "serial" else "parallel";
    if Flags.getConfigEnum(Flags.LINK_TYPE) == 1 then
      linkType := "static";
    elseif Flags.getConfigEnum(Flags.LINK_TYPE) == 2 then
      linkType := "dynamic";
    end if;
    linkType := if Testsuite.isRunning() then "dynamic" else linkType;
    s_call := stringAppendList({omhome,cdWorkingDir,setMakeVars,"\"",omhome_1,pd,"share",pd,"omc",pd,"scripts",pd,"Compile","\""," ",fileprefix," ",Config.simulationCodeTarget()," ", System.openModelicaPlatform(), " ", winCompileMode, " ", linkType});
  else
    numParallel := if Testsuite.isRunning() then 1 else Config.noProc();
    cdWorkingDir := if stringEmpty(workingDir) then "" else (" -C \"" + workingDir + "\"");
    setMakeVars := sum(" "+var for var in makeVarsNoBinding);
    s_call := stringAppendList({Autoconf.make," -j",intString(numParallel),cdWorkingDir," -f ",fileprefix,".makefile",setMakeVars});
  end if;
  if Flags.isSet(Flags.DYN_LOAD) then
    Debug.traceln("compileModel: running " + s_call);
  end if;

  // remove .exe .dll .log!
  if System.regularFileExists(fileEXE) then
    0 := System.removeFile(fileEXE);
  end if;
  if System.regularFileExists(fileDLL) then
    0 := System.removeFile(fileDLL);
  end if;
  if System.regularFileExists(fileLOG) then
    0 := System.removeFile(fileLOG);
  end if;

  if Testsuite.isRunning() then
    System.appendFile(Testsuite.getTempFilesFile(),
      fileEXE + "\n" + fileDLL + "\n" + fileLOG + "\n" + fileprefix + ".o\n" + fileprefix + ".libs\n" +
      fileprefix + "_records.o\n" + fileprefix + "_res.mat\n");
  end if;

  // call the system command to compile the model!
  if System.systemCall(s_call,if isWindows then "" else fileLOG) <> 0 then
    // We failed, print error
    if System.regularFileExists(fileLOG) then
      Error.addMessage(Error.SIMULATOR_BUILD_ERROR, {System.readFile(fileLOG)});
    elseif isWindows then
      // Check that it is a correct OPENMODELICAHOME, on Windows only
      s_call := stringAppendList({omhome_1,pd,"share",pd,"omc",pd,"scripts",pd,"Compile.bat"});
      if not System.regularFileExists(s_call) then
        Error.addMessage(Error.SIMULATOR_BUILD_ERROR, {stringAppendList({"command ",s_call," not found. Check $OPENMODELICAHOME"})});
      end if;
    end if;
    if Flags.isSet(Flags.DYN_LOAD) then
      Debug.trace("compileModel: failed!\n");
    end if;
    fail();
  end if;

  if Flags.isSet(Flags.DYN_LOAD) then
    Debug.trace("compileModel: successful!\n");
  end if;
end compileModel;

public function loadFile "load the file or the directory structure if the file is named package.mo"
  input String name;
  input String encoding;
  input Absyn.Program p;
  input Boolean checkUses;
  input Boolean notifyLoad;
  input Boolean requireExactVersion;
  output Absyn.Program outProgram;
protected
  String dir,filename,cname,prio,mp;
  list<String> rest;
algorithm
  true := System.regularFileExists(name);
  (dir,filename) := Util.getAbsoluteDirectoryAndFile(name);
  if filename == "package.mo" or filename == "package.moc" then
    cname::rest := System.strtok(List.last(System.strtok(dir,"/"))," ");
    prio := stringDelimitList(rest, " ");
    // send "" priority if that is it, don't send "default"
    // see https://trac.openmodelica.org/OpenModelica/ticket/2422
    // prio = if_(stringEq(prio,""), "default", prio);
    mp := System.realpath(dir + "/../") + Autoconf.groupDelimiter + Settings.getModelicaPath(Testsuite.isRunning());
    (outProgram,true) := loadModel((Absyn.IDENT(cname),"loadFile automatically converted to loadModel",{prio},true)::{}, mp, p, true, notifyLoad, checkUses, requireExactVersion, filename == "package.moc", pathToFile=System.realpath(name));
    return;
  end if;
  outProgram := Parser.parse(name,encoding);
  ClassLoader.checkOnLoadMessage(outProgram);

  // Check if we have duplicate top level classes before calling checkUsesAndUpdateProgram()
  // which will update the program by repeatedly replacing duplicate classes with last one seen.
  if checkDuplicateTopLevelClasses(outProgram) then
    fail();
  end if;

  outProgram := checkUsesAndUpdateProgram(outProgram, p, checkUses, Settings.getModelicaPath(Testsuite.isRunning()), notifyLoad, requireExactVersion);
end loadFile;

protected function checkDuplicateTopLevelClasses
  input Absyn.Program program;
  output Boolean hasDuplicates = false;
protected
  Boolean skip;
  list<SourceInfo> infos;
  UnorderedMap<String, Absyn.Info> classInfoMap;
  Option<Absyn.Info> optClassInfo;
algorithm

  if listLength(program.classes) < 2 then
    return;
  end if;

  classInfoMap := UnorderedMap.new<Absyn.Info>(stringHashDjb2, stringEq);
  for cl in program.classes loop
    _ := match cl
      case Absyn.CLASS(info=SOURCEINFO())
        algorithm
          // If the class comes from the interactive env or builtin files, ignore it.
          skip := stringEq(cl.info.fileName,"<interactive>")
                  or stringEq(System.basename(cl.info.fileName), "ModelicaBuiltin.mo")
                  or stringEq(System.basename(cl.info.fileName), "MetaModelicaBuiltin.mo");

          if not skip then
            optClassInfo := UnorderedMap.get(cl.name, classInfoMap);

            if Util.isSome(optClassInfo) then
              // It is a duplicate named top level class. Print error and return.
              infos := {Util.getOption(optClassInfo), cl.info};
              Error.addMultiSourceMessage(Error.DOUBLE_DECLARATION_OF_ELEMENTS, {cl.name}, infos);
              hasDuplicates := true;
              return;
            else
              // It is not a duplicate yet. Add it to the map and continue.
              UnorderedMap.add(cl.name, cl.info, classInfoMap);
            end if;

          end if;
        then ();
      else ();
    end match;
  end for;
end checkDuplicateTopLevelClasses;

protected function checkUsesAndUpdateProgram
  input Absyn.Program newp;
  input output Absyn.Program p;
  input Boolean checkUses;
  input String modelicaPath;
  input Boolean notifyLoad;
  input Boolean requireExactVersion;
protected
  list<tuple<Absyn.Path,String,list<String>,Boolean>> modelsToLoad;
algorithm
  modelsToLoad := if checkUses then Interactive.getUsesAnnotationOrDefault(newp, requireExactVersion) else {};
  p := InteractiveUtil.updateProgram(newp, p);
  (p, _) := loadModel(modelsToLoad, modelicaPath, p, false, notifyLoad, checkUses, requireExactVersion, false);
end checkUsesAndUpdateProgram;

public function loadModel
  input list<tuple<Absyn.Path,String,list<String>,Boolean /* Only use the first entry on the MODELICAPATH */>> imodelsToLoad;
  input String modelicaPath;
  input Absyn.Program ip;
  input Boolean forceLoad;
  input Boolean notifyLoad;
  input Boolean checkUses;
  input Boolean requireExactVersion;
  input Boolean encrypted = false;
  input String pathToFile = "";
  output Absyn.Program pnew = ip;
  output Boolean success = true;
protected
  Boolean b;
algorithm
  PackageManagement.installCachedPackages();

  for m in imodelsToLoad loop
    (pnew, b) := loadModel1(m, modelicaPath, forceLoad, notifyLoad,
      checkUses, requireExactVersion, encrypted, pathToFile, pnew);
    success := b and success;
  end for;
end loadModel;

protected function loadModel1
  input tuple<Absyn.Path,String,list<String>,Boolean> modelToLoad;
  input String modelicaPath;
  input Boolean forceLoad;
  input Boolean notifyLoad;
  input Boolean checkUses;
  input Boolean requireExactVersion;
  input Boolean encrypted;
  input String pathToFile;
  input output Absyn.Program program;
        output Boolean success = true;
protected
  list<tuple<Absyn.Path,String,list<String>,Boolean>> modelsToLoad;
  Boolean onlyCheckFirstModelicaPath;
  Absyn.Path path;
  list<String> versionsLst;
  String pathStr, versions, version, thisModelicaPath, dir, requestedBy;
  Absyn.Program pnew;
  ErrorTypes.MessageTokens msgTokens;
  Option<Absyn.Class> cl;
algorithm
  (path, requestedBy, versionsLst, onlyCheckFirstModelicaPath) := modelToLoad;
  if onlyCheckFirstModelicaPath then
    /* Using loadFile() */
    thisModelicaPath::_ := System.strtok(modelicaPath, Autoconf.groupDelimiter);
  else
    thisModelicaPath := modelicaPath;
  end if;
  try
    if checkModelLoaded(modelToLoad, program, forceLoad, NONE()) then
      pnew := Absyn.PROGRAM({}, Absyn.TOP());
      version := "";
    else
      if pathToFile=="" then
        pnew := ClassLoader.loadClass(path, versionsLst, thisModelicaPath, NONE(), requireExactVersion, encrypted);
      else
        dir := System.dirname(pathToFile);
        cl := ClassLoader.loadClassFromMp(AbsynUtil.pathFirstIdent(path), System.dirname(dir), System.basename(dir), true, NONE(), encrypted);
        if (isSome(cl)) then
          pnew := Absyn.PROGRAM({Util.getOption(cl)}, Absyn.TOP());
        else
          pnew := Absyn.PROGRAM({}, Absyn.TOP());
        end if;
      end if;

      if notifyLoad and not forceLoad then
        version := getPackageVersion(path, pnew);
        msgTokens := {AbsynUtil.pathString(path), version, requestedBy};
        Error.addMessage(Error.NOTIFY_LOAD_MODEL_DUE_TO_USES, msgTokens);
        System.loadModelCallBack(AbsynUtil.pathFirstIdent(path));
      end if;
    end if;

    program := InteractiveUtil.updateProgram(pnew, program);

    if checkUses then
      modelsToLoad := Interactive.getUsesAnnotationOrDefault(pnew, requireExactVersion);
      (program, success) := loadModel(modelsToLoad, modelicaPath, program, false, notifyLoad, checkUses, requireExactVersion, false);
    end if;
  else
    pathStr := AbsynUtil.pathString(path);
    versions := stringDelimitList(versionsLst, ",");
    msgTokens := {pathStr, versions, thisModelicaPath};
    if forceLoad then
      Error.addMessage(Error.LOAD_MODEL_FAILED, msgTokens);
      success := false;
    else
      Error.addMessage(Error.NOTIFY_LOAD_MODEL_FAILED, msgTokens);
    end if;
  end try;
end loadModel1;

protected function checkModelLoaded
  input tuple<Absyn.Path,String,list<String>,Boolean> tpl;
  input Absyn.Program p;
  input Boolean forceLoad;
  input Option<String> failNonLoad;
  output Boolean loaded;
algorithm
  loaded := matchcontinue (tpl,p,forceLoad,failNonLoad)
    local
      Absyn.Class cdef;
      String str1,str2,requestOrigin;
      Option<String> ostr2;
      Absyn.Path path;
      list<String> withoutConversion,withConversion;

    case (_,_,true,_) then false;
    case ((path,requestOrigin,str1::_,_),_,false,_)
      equation
        cdef = InteractiveUtil.getPathedClassInProgram(path,p);
        ostr2 = AbsynUtil.getNamedAnnotationInClass(cdef,Absyn.IDENT("version"),Interactive.getAnnotationStringValueOrFail);
        (withoutConversion,withConversion) = Interactive.getConversionAnnotation(cdef);
        checkValidVersion(path,str1,ostr2,requestOrigin=requestOrigin,withConversion=withConversion,withoutConversion=withoutConversion);
      then true;
    case (_,_,_,NONE()) then false;
    case ((path,_,_,_),_,_,SOME(str2))
      equation
        str1 = AbsynUtil.pathString(path);
        Error.addMessage(Error.INST_NON_LOADED, {str1,str2});
      then false;
  end matchcontinue;
end checkModelLoaded;

protected function checkValidVersion
  input Absyn.Path path;
  input String version;
  input Option<String> actualVersion;
  input String requestOrigin;
  input list<String> withConversion;
  input list<String> withoutConversion;
protected
  SemanticVersion.Version semverWanted, semverActual;
  String actualVersionStr, pathStr;
algorithm
  semverWanted := SemanticVersion.parse(version);
  _ := match actualVersion
    case SOME(actualVersionStr)
      then ();
    case NONE()
      algorithm
        actualVersionStr := "";
      then ();
  end match;
  pathStr := AbsynUtil.pathString(path);
  semverActual := SemanticVersion.parse(actualVersionStr);
  if 0 == SemanticVersion.compare(semverWanted, semverActual, comparePrerelease=false) then
    return;
  end if;
  if not (SemanticVersion.isSemVer(semverActual) and SemanticVersion.isSemVer(semverWanted)) then
    Error.addMessage(Error.LOAD_MODEL_DIFFERENT_VERSIONS, {pathStr, version, actualVersionStr});
    return;
  end if;
  for ver in withoutConversion loop
    if 0 == SemanticVersion.compare(semverWanted, SemanticVersion.parse(ver), comparePrerelease=false) then
      Error.addMessage(Error.LOAD_MODEL_DIFFERENT_VERSIONS_WITHOUT_CONVERSION, {requestOrigin, pathStr, version, actualVersionStr});
      return;
    end if;
  end for;
  for ver in withConversion loop
    if 0 == SemanticVersion.compare(semverWanted, SemanticVersion.parse(ver), comparePrerelease=false) then
      Error.addMessage(Error.LOAD_MODEL_DIFFERENT_VERSIONS_WITH_CONVERSION, {requestOrigin, pathStr, version, actualVersionStr});
      return;
    end if;
  end for;
  if SemanticVersion.compare(semverWanted, semverActual) > 0 then
    Error.addMessage(Error.LOAD_MODEL_DIFFERENT_VERSIONS_NEWER, {pathStr, version, actualVersionStr});
  else
    Error.addMessage(Error.LOAD_MODEL_DIFFERENT_VERSIONS_OLDER, {pathStr, version, actualVersionStr});
  end if;
end checkValidVersion;

public function cevalInteractiveFunctions
"defined in the interactive environment."
  input FCore.Cache inCache;
  input FCore.Graph inEnv;
  input DAE.Exp inExp "expression to evaluate";
  input Absyn.Msg msg;
  input Integer numIter;
  output FCore.Cache outCache;
  output Values.Value outValue;
algorithm
  (outCache,outValue) := matchcontinue (inCache,inEnv,inExp,msg,numIter)
    local
      FCore.Cache cache;
      FCore.Graph env;
      DAE.Exp exp;
      list<DAE.Exp> eLst;
      list<Values.Value> valLst;
      String name;
      Values.Value value;
      Real t1,t2,t;

      // This needs to be first because otherwise it takes 0 time to get the value :)
    case (cache,env,DAE.CALL(path = Absyn.IDENT(name = "timing"),expLst = {exp}),_,_)
      equation
        t1 = System.time();
        (cache,_) = Ceval.ceval(cache,env, exp, true, msg,numIter+1);
        t2 = System.time();
        t = t2 - t1;
      then
        (cache,Values.REAL(t));

    case (cache,env,DAE.CALL(path=Absyn.IDENT(name),attr=DAE.CALL_ATTR(builtin=true),expLst=eLst),_,_)
      equation
        (cache,valLst) = Ceval.cevalList(cache,env,eLst,true,msg,numIter);
        valLst = List.map1(valLst,evalCodeTypeName,env);
        (cache,value) = cevalInteractiveFunctions2(cache,env,name,valLst,msg);
      then
        (cache,value);

  end matchcontinue;
end cevalInteractiveFunctions;

public function cevalInteractiveFunctions2
"defined in the interactive environment."
  input FCore.Cache cache;
  input FCore.Graph env;
  input String functionName;
  input list<Values.Value> args;
  input Absyn.Msg msg;
  output FCore.Cache outCache = cache;
  output Values.Value outValue;
algorithm
  outValue := matchcontinue (functionName, args)
    local
      String str,str1,str2,str3,token,cmd,encoding,filename,pathstr,name,res,workdir,from,to;
      list<Values.Value> vals, cvars;
      Absyn.Path path,classpath,className,parentClass;
      SCode.Program sp;
      Absyn.Program p,newp;
      list<Absyn.Program> newps;
      DAE.Type ty;
      list<DAE.Type> tys;
      Values.Value v;
      Integer i, access;
      list<String> strs, strs1, strs2, interfaceType;
      Real r,r1,r2;
      Boolean bval, b, b1, mergeAST, includePartial, qualified, sort, requireExactVersion;
      Absyn.CodeNode codeNode;
      list<Absyn.Path> paths;
      list<Absyn.Class> classes;
      Absyn.Within within_;
      Config.LanguageStandard oldLanguageStd;
      SCode.Element cl;
      list<SCode.Element> elts;
      list<ErrorTypes.TotalMessage> messages;
      list<tuple<String,list<String>>> interfaceTypeAssoc;
      GCExt.ProfStats gcStats;

    case ("parseString",{Values.STRING(str1),Values.STRING(str2)})
      algorithm
        Absyn.PROGRAM(classes=classes,within_=within_) := Parser.parsestring(str1,str2);
        paths := list(Absyn.Path.IDENT(AbsynUtil.className(c)) for c in classes);
        paths := List.map1r(paths,AbsynUtil.joinWithinPath,within_);
        vals := List.map(paths,ValuesUtil.makeCodeTypeName);
      then
        ValuesUtil.makeArray(vals);

    case ("parseString",_)
      then ValuesUtil.makeArray({});

    case ("parseFile",{Values.STRING(str1),Values.STRING(encoding)})
      algorithm
        // clear the errors before!
        Error.clearMessages() "Clear messages";
        Print.clearErrorBuf() "Clear error buffer";
        paths := Interactive.parseFile(str1, encoding);
        vals := List.map(paths,ValuesUtil.makeCodeTypeName);
      then
        ValuesUtil.makeArray(vals);

    case ("loadFileInteractiveQualified",{Values.STRING(str1),Values.STRING(encoding)})
      algorithm
        // clear the errors before!
        Error.clearMessages() "Clear messages";
        Print.clearErrorBuf() "Clear error buffer";
        paths := Interactive.parseFile(str1, encoding, updateProgram=true);
        vals := List.map(paths,ValuesUtil.makeCodeTypeName);
      then
        ValuesUtil.makeArray(vals);

    case ("loadFileInteractive",{Values.STRING(str1),Values.STRING(encoding),Values.BOOL(b),Values.BOOL(b1),Values.BOOL(requireExactVersion)})
      algorithm
        newp := loadFile(str1, encoding, SymbolTable.getAbsyn(), b, b1, requireExactVersion) "System.regularFileExists(name) => 0 &    Parser.parse(name) => p1 &" ;
        vals := List.map(Interactive.getTopClassnames(newp),ValuesUtil.makeCodeTypeName);
        SymbolTable.setAbsyn(newp);
      then
        ValuesUtil.makeArray(vals);

    case ("getSourceFile",{Values.CODE(Absyn.C_TYPENAME(path))})
      algorithm
        str := Interactive.getSourceFile(path, SymbolTable.getAbsyn());
      then
        Values.STRING(str);

    case ("setSourceFile",{Values.CODE(Absyn.C_TYPENAME(path)),Values.STRING(str)})
      algorithm
        Values.ENUM_LITERAL(index=access) := Interactive.checkAccessAnnotationAndEncryption(path, SymbolTable.getAbsyn());
        if (access >= 9) then // i.e., The class is not encrypted.
          (b,p) := Interactive.setSourceFile(path, str, SymbolTable.getAbsyn());
          SymbolTable.setAbsyn(p);
        else
          Error.addMessage(Error.SAVE_ENCRYPTED_CLASS_ERROR, {});
          b := false;
        end if;
      then
        Values.BOOL(b);

    case ("basename",{Values.STRING(str)})
      then Values.STRING(System.basename(str));

    case ("dirname",{Values.STRING(str)})
      then Values.STRING(System.dirname(str));

    case ("codeToString",{Values.CODE(codeNode)})
      then Values.STRING(Dump.printCodeStr(codeNode));

    case ("typeOf",{Values.CODE(Absyn.C_VARIABLENAME(Absyn.CREF_IDENT(name = name)))})
      algorithm
        ty := Interactive.getTypeOfVariable(name, SymbolTable.getVars());
      then
        Values.STRING(Types.unparseType(ty));

    case ("GC_gcollect_and_unmap",{})
      algorithm
        GCExt.gcollectAndUnmap();
      then
        Values.BOOL(true);

    case ("GC_expand_hp",{Values.INTEGER(i)})
      then Values.BOOL(GCExt.expandHeap(i));

    case ("GC_set_max_heap_size",{Values.INTEGER(i)})
      algorithm
        GCExt.setMaxHeapSize(i);
      then
        Values.BOOL(true);

    case ("GC_get_prof_stats",{})
      algorithm
        gcStats := GCExt.getProfStats();
      then
        Values.RECORD(Absyn.IDENT("GC_PROFSTATS"),
         {
           Values.INTEGER(gcStats.heapsize_full),
           Values.INTEGER(gcStats.free_bytes_full),
           Values.INTEGER(gcStats.unmapped_bytes),
           Values.INTEGER(gcStats.bytes_allocd_since_gc),
           Values.INTEGER(gcStats.allocd_bytes_before_gc),
           Values.INTEGER(gcStats.bytes_allocd_since_gc+gcStats.allocd_bytes_before_gc),
           Values.INTEGER(gcStats.non_gc_bytes),
           Values.INTEGER(gcStats.gc_no),
           Values.INTEGER(gcStats.markers_m1),
           Values.INTEGER(gcStats.bytes_reclaimed_since_gc),
           Values.INTEGER(gcStats.reclaimed_bytes_before_gc)
         },
         {
           "heapsize_full",
           "free_bytes_full",
           "unmapped_bytes",
           "bytes_allocd_since_gc",
           "allocd_bytes_before_gc",
           "total_allocd_bytes",
           "non_gc_bytes",
           "gc_no",
           "markers_m1",
           "bytes_reclaimed_since_gc",
           "reclaimed_bytes_before_gc"
         },
         -1);

    case ("clear",{})
      algorithm
        SymbolTable.reset();
      then
        Values.BOOL(true);

    case ("clearProgram",{})
      algorithm
        SymbolTable.clearProgram();
      then
        Values.BOOL(true);

    case ("clearVariables",{})
      algorithm
        SymbolTable.setVars({});
      then
        Values.BOOL(true);

    case ("list", _) then listClass(args);
    case ("listFile", _) then listFile(args);

    case ("sortStrings",{Values.ARRAY(valueLst=vals)})
      algorithm
        strs := List.map(vals, ValuesUtil.extractValueString);
        strs := List.sort(strs, Util.strcmpBool);
      then
        ValuesUtil.makeArray(List.map(strs,ValuesUtil.makeString));

    case ("listVariables",{})
      then ValuesUtil.makeArray(getVariableNames(SymbolTable.getVars(),{}));

    case ("setCompileCommand",{Values.STRING(cmd)})
      algorithm
        // cmd = Util.rawStringToInputString(cmd);
        Settings.setCompileCommand(cmd);
      then
        Values.BOOL(true);

    case ("getCompileCommand",{})
      then Values.STRING(Settings.getCompileCommand());

    case ("setTempDirectoryPath",{Values.STRING(cmd)})
      algorithm
        // cmd = Util.rawStringToInputString(cmd);
        Settings.setTempDirectoryPath(cmd);
      then
        Values.BOOL(true);

    case ("getTempDirectoryPath",{})
      then Values.STRING(Settings.getTempDirectoryPath());

    case ("setEnvironmentVar",{Values.STRING(name), Values.STRING(str)})
      then Values.BOOL(System.setEnv(name, str, true) == 0);

    case ("getEnvironmentVar",{Values.STRING(name)})
      then Values.STRING(Util.makeValueOrDefault(System.readEnv, name, ""));

    case ("setInstallationDirectoryPath",{Values.STRING(cmd)})
      algorithm
        // cmd = Util.rawStringToInputString(cmd);
        Settings.setInstallationDirectoryPath(cmd);
      then
        Values.BOOL(true);

    case ("getInstallationDirectoryPath",{})
      then Values.STRING(Settings.getInstallationDirectoryPath());

    case ("getModelicaPath",{})
      then Values.STRING(Settings.getModelicaPath(Testsuite.isRunning()));

    case ("setModelicaPath",{Values.STRING(cmd)})
      algorithm
        // cmd = Util.rawStringToInputString(cmd);
        Settings.setModelicaPath(cmd);
        setGlobalRoot(Global.packageIndexCacheIndex, 0);
      then
        Values.BOOL(true);

    case ("setModelicaPath",_)
      then Values.BOOL(false);

    case ("getHomeDirectoryPath",{})
      then Values.STRING(Settings.getHomeDir(Testsuite.isRunning()));

    case ("getLanguageStandard",{})
      then Values.STRING(Config.languageStandardString(Config.getLanguageStandard()));

    case ("reopenStandardStream",{Values.ENUM_LITERAL(index=i),Values.STRING(filename)})
      then Values.BOOL(System.reopenStandardStream(i-1,filename));

    case ("iconv",{Values.STRING(str),Values.STRING(from),Values.STRING(to)})
      then Values.STRING(System.iconv(str,from,to));

    case ("getCompiler",{})
      then Values.STRING(System.getCCompiler());

    case ("setCFlags",{Values.STRING(str)})
      algorithm
        System.setCFlags(str);
      then
        Values.BOOL(true);

    case ("getCFlags",{})
      then Values.STRING(System.getCFlags());

    case ("setCompiler",{Values.STRING(str)})
      algorithm
        System.setCCompiler(str);
      then
        Values.BOOL(true);

    case ("getCXXCompiler",{})
      then Values.STRING(System.getCXXCompiler());

    case ("setCXXCompiler",{Values.STRING(str)})
      algorithm
        System.setCXXCompiler(str);
      then
        Values.BOOL(true);

    case ("setCompilerFlags",{Values.STRING(str)})
      algorithm
        System.setCFlags(str);
      then
        Values.BOOL(true);

    case ("getLinker",{})
      then Values.STRING(System.getLinker());

    case ("setLinker",{Values.STRING(str)})
      algorithm
        System.setLinker(str);
      then
        Values.BOOL(true);

    case ("getLinkerFlags",{})
      then Values.STRING(System.getLDFlags());

    case ("setLinkerFlags",{Values.STRING(str)})
      algorithm
        System.setLDFlags(str);
      then
        Values.BOOL(true);

    case ("setCommandLineOptions",{Values.STRING(str)})
      algorithm
        b := Flags.isSet(Flags.SCODE_INST);
        strs := System.strtok(str, " ");
        {} := FlagsUtil.readArgs(strs);
        outCache := FCore.emptyCache();

        if b <> Flags.isSet(Flags.SCODE_INST) then
          Builtin.clearInitialGraph();
        end if;
      then
        Values.BOOL(true);

    case ("setCommandLineOptions",_)
      then Values.BOOL(false);

    case ("getCommandLineOptions", {})
      then ValuesUtil.makeStringArray(FlagsUtil.unparseFlags());

    case ("getCommandLineOptions", _)
      then Values.META_FAIL();

    case ("clearCommandLineOptions", {})
      algorithm
        FlagsUtil.resetDebugFlags();
        FlagsUtil.resetConfigFlags();
      then
        Values.BOOL(true);

    case ("clearCommandLineOptions",_)
      then Values.BOOL(false);

    case ("enableNewInstantiation",_)
      algorithm
        if not Flags.isSet(Flags.SCODE_INST) then
          Builtin.clearInitialGraph();
          FlagsUtil.enableDebug(Flags.SCODE_INST);
          outCache := FCore.emptyCache();
        end if;
      then
        Values.BOOL(true);

    case ("enableNewInstantiation",_)
      then Values.BOOL(false);

    case ("disableNewInstantiation",_)
      algorithm
        if Flags.isSet(Flags.SCODE_INST) then
          FlagsUtil.disableDebug(Flags.SCODE_INST);
          outCache := FCore.emptyCache();
          Builtin.clearInitialGraph();
        end if;
      then
        Values.BOOL(true);

    case ("disableNewInstantiation",_)
      then
        Values.BOOL(false);

    case ("clearDebugFlags",_)
      algorithm
        FlagsUtil.resetDebugFlags();
      then
        Values.BOOL(true);

    case ("clearDebugFlags",_)
      then Values.BOOL(false);

    case ("getConfigFlagValidOptions",{Values.STRING(str)})
      algorithm
        (strs1, str, strs2) := FlagsUtil.getValidOptionsAndDescription(str);
      then
        Values.TUPLE({
          ValuesUtil.makeStringArray(strs1),
          Values.STRING(str),
          ValuesUtil.makeStringArray(strs2)
        });

    case ("getConfigFlagValidOptions",{Values.STRING(_)})
      then
        Values.TUPLE({
          ValuesUtil.makeArray({}),
          Values.STRING(""),
          ValuesUtil.makeArray({})
        });

    case ("cd",{Values.STRING("")})
      then Values.STRING(System.pwd());

    case ("cd",{Values.STRING(str)})
      algorithm
        0 := System.cd(str);
      then
        Values.STRING(System.pwd());

    case ("cd",{Values.STRING(str)})
      algorithm
        false := System.directoryExists(str);
        res := stringAppendList({"Error, directory ",str," does not exist,"});
      then
        Values.STRING(res);

    case ("mkdir",{Values.STRING(str)})
      algorithm
        true := System.directoryExists(str);
      then
        Values.BOOL(true);

    case ("mkdir",{Values.STRING(str)})
      then Values.BOOL(Util.createDirectoryTree(str));

    case ("copy",{Values.STRING(str1),Values.STRING(str2)})
      then Values.BOOL(System.copyFile(str1,str2));

    case ("remove",{Values.STRING(str)})
      then Values.BOOL(System.removeDirectory(str));

    case ("getVersion",{Values.CODE(Absyn.C_TYPENAME(Absyn.IDENT("OpenModelica")))})
      then Values.STRING(Settings.getVersionNr());

    case ("getVersion",{Values.CODE(Absyn.C_TYPENAME(path))})
      then Values.STRING(getPackageVersion(path,SymbolTable.getAbsyn()));

    case ("getTempDirectoryPath",{})
      then Values.STRING(Settings.getTempDirectoryPath());

    case ("system",{Values.STRING(str),Values.STRING(filename)})
      then Values.INTEGER(System.systemCall(str,filename));

    case ("system_parallel",{Values.ARRAY(valueLst=vals),Values.INTEGER(i)})
      algorithm
        strs := List.map(vals, ValuesUtil.extractValueString);
      then
        ValuesUtil.makeIntArray(System.systemCallParallel(strs,i));

    case ("timerClear",{Values.INTEGER(i)})
      algorithm
        System.realtimeClear(i);
      then
        Values.NORETCALL();

    case ("timerTick",{Values.INTEGER(i)})
      algorithm
        System.realtimeTick(i);
      then
        Values.NORETCALL();

    case ("timerTock",{Values.INTEGER(i)})
      algorithm
        true := System.realtimeNtick(i) > 0;
      then
        Values.REAL(System.realtimeTock(i));

    case ("timerTock",_)
      then Values.REAL(-1.0);

    case ("readFile",{Values.STRING(str)})
      then Values.STRING(System.readFile(str));

    case ("readFile",_)
      then Values.STRING("");

    case ("writeFile",{Values.STRING(str),Values.STRING(str1),Values.BOOL(false)})
      algorithm
        System.writeFile(str,str1);
      then
        Values.BOOL(true);

    case ("writeFile",{Values.STRING(str),Values.STRING(str1),Values.BOOL(true)})
      algorithm
        System.appendFile(str, str1);
      then
        Values.BOOL(true);

    case ("writeFile",_)
      then Values.BOOL(false);

    case ("deleteFile",{Values.STRING(str)})
      then Values.BOOL(System.removeFile(str) == 0);

    case ("compareFiles",{Values.STRING(str1),Values.STRING(str2)})
      then Values.BOOL(System.fileContentsEqual(str1,str2));

    case ("compareFilesAndMove",{Values.STRING(str1),Values.STRING(str2)})
      algorithm
        true := System.regularFileExists(str1);
        b := System.regularFileExists(str2) and System.fileContentsEqual(str1,str2);
        b := if not b then System.rename(str1,str2) else System.removeFile(str1) == 0;
      then
        Values.BOOL(b);

    case ("compareFilesAndMove",_)
      then Values.BOOL(false);

    case ("readFileNoNumeric",{Values.STRING(str)})
      then Values.STRING(System.readFileNoNumeric(str));

    case ("getErrorString",{Values.BOOL(b)})
      then Values.STRING(Error.printMessagesStr(b));

    case ("countMessages",_)
      then Values.TUPLE({
        Values.INTEGER(Error.getNumMessages()),
        Values.INTEGER(Error.getNumErrorMessages()),
        Values.INTEGER(ErrorExt.getNumWarningMessages())
      });

    case ("clearMessages",{})
      algorithm
        Error.clearMessages();
      then
        Values.BOOL(true);

    case ("getMessagesStringInternal",{Values.BOOL(true)})
      algorithm
        messages := List.unique(Error.getMessages());
      then
        ValuesUtil.makeArray(List.map(messages, errorToValue));

    case ("getMessagesStringInternal",{Values.BOOL(false)})
      then ValuesUtil.makeArray(List.map(Error.getMessages(), errorToValue));

    case ("stringTypeName",{Values.STRING(str)})
      then Values.CODE(Absyn.C_TYPENAME(Parser.stringPath(str)));

    case ("stringVariableName",{Values.STRING(str)})
      then Values.CODE(Absyn.C_VARIABLENAME(Parser.stringCref(str)));

    case ("typeNameString",{Values.CODE(A=Absyn.C_TYPENAME(path=path))})
      then Values.STRING(AbsynUtil.pathString(path));

    case ("typeNameStrings",{Values.CODE(A=Absyn.C_TYPENAME(path=path))})
      then ValuesUtil.makeArray(List.map(AbsynUtil.pathToStringList(path),ValuesUtil.makeString));

    case ("generateHeader",{Values.STRING(filename)})
      algorithm
        str := Tpl.tplString(Unparsing.programExternalHeader, SymbolTable.getSCode());
        System.writeFile(filename,str);
      then
        Values.BOOL(true);

    case ("generateHeader",_)
      then Values.BOOL(false);

    case ("generateJuliaHeader",{Values.STRING(filename)})
      algorithm
        str := Tpl.tplString(Unparsing.programExternalHeaderJulia, SymbolTable.getSCode());
        System.writeFile(filename,str);
      then
        Values.BOOL(true);

    case ("generateJuliaHeader",_)
      then Values.BOOL(false);

    case ("generateCode",{Values.CODE(Absyn.C_TYPENAME(path))})
      algorithm
        (outCache, Util.SUCCESS()) := Static.instantiateDaeFunction(outCache, env, path, false, NONE(), true);
        outCache := cevalGenerateFunction(outCache,env,SymbolTable.getAbsyn(),path);
      then
        Values.BOOL(true);

    case ("generateCode",_)
      then Values.BOOL(false);

    case ("generateScriptingAPI",{Values.CODE(Absyn.C_TYPENAME(className)), Values.STRING(name)})
      algorithm
        sp := SymbolTable.getSCode();
        elts := match FBuiltin.getElementWithPathCheckBuiltin(sp, className)
          case SCode.CLASS(classDef=SCode.PARTS(elementLst=elts)) then elts;
          case cl equation Error.addSourceMessage(Error.INTERNAL_ERROR, {AbsynUtil.pathString(className) + " does not contain SCode.PARTS"}, SCodeUtil.elementInfo(cl)); then fail();
        end match;
        tys := {};
        for elt in elts loop
          _ := matchcontinue elt
            case SCode.CLASS(partialPrefix=SCode.NOT_PARTIAL(), restriction=SCode.R_FUNCTION(SCode.FR_EXTERNAL_FUNCTION()))
              algorithm
                (outCache, ty, _) := Lookup.lookupType(outCache, env, AbsynUtil.suffixPath(className, elt.name), NONE() /*SOME(elt.info)*/);
                if isSimpleAPIFunction(ty) then
                  tys := ty::tys;
                end if;
              then ();
            else ();
          end matchcontinue;
        end for;
        str1 := Tpl.tplString(GenerateAPIFunctionsTpl.getCevalScriptInterface, tys);
        str2 := Tpl.tplString3(GenerateAPIFunctionsTpl.getQtInterface, tys, name + "::", name);
        str3 := Tpl.tplString2(GenerateAPIFunctionsTpl.getQtInterfaceHeaders, tys, name);
      then
        Values.TUPLE({Values.BOOL(true),Values.STRING(str1),Values.STRING(str2),Values.STRING(str3)});

    case ("generateScriptingAPI",_)
      then Values.TUPLE({Values.BOOL(false),Values.STRING(""),Values.STRING("")});

    case ("generateEntryPoint",{Values.STRING(filename),Values.CODE(Absyn.C_TYPENAME(path)),Values.STRING(str)})
      algorithm
        str := Tpl.tplString2(CodegenCFunctions.generateEntryPoint, path, str);
        System.writeFile(filename,str);
      then
        Values.BOOL(true);

    case ("generateEntryPoint", _)
      then Values.BOOL(false);

    case ("checkInterfaceOfPackages",{Values.CODE(Absyn.C_TYPENAME(path)),Values.ARRAY(valueLst=vals)})
      algorithm
        sp := SymbolTable.getSCode();
        cl := SCodeUtil.getElementWithPath(sp,path);
        interfaceTypeAssoc := List.map1(vals, getInterfaceTypeAssocElt, SCodeUtil.elementInfo(cl));
        interfaceType := getInterfaceType(cl, interfaceTypeAssoc);
        List.map1_0(sp, verifyInterfaceType, interfaceType);
      then
        Values.BOOL(true);

    case ("checkInterfaceOfPackages",_)
      then Values.BOOL(false);

    case ("generateSeparateCodeDependenciesMakefile",_)
      then generateSeparateCodeDependenciesMakefile(args);

    case ("generateSeparateCodeDependencies",_)
      then generateSeparateCodeDependencies(args);

    case ("generateSeparateCode", _)
      algorithm
        (v, outCache) := generateSeparateCode(args, outCache, env);
      then
        v;

    case ("getImportedNames",{Values.CODE(Absyn.C_TYPENAME(path))})
      algorithm
        (vals, cvars) := getImportedNames(InteractiveUtil.getPathedClassInProgram(path, SymbolTable.getAbsyn()));
        v := Values.TUPLE({ValuesUtil.makeArray(vals),ValuesUtil.makeArray(cvars)});
      then
        v;

    case ("getImportedNames",_)
      then (Values.TUPLE({ValuesUtil.makeArray({}),ValuesUtil.makeArray({})}));

    case ("getMMfileTotalDependencies",{Values.STRING(str1), Values.STRING(str2)})
      algorithm
        strs := getMMfileTotalDependencies(str1, str2);
        vals := list(Values.STRING(s) for s in strs);
      then
        ValuesUtil.makeArray(vals);

    case ("getMMfileTotalDependencies",_)
      then ValuesUtil.makeArray({});

    case ("loadModel",{Values.CODE(Absyn.C_TYPENAME(path)),Values.ARRAY(valueLst=cvars),Values.BOOL(b),Values.STRING(str),Values.BOOL(requireExactVersion)})
      algorithm
        p := SymbolTable.getAbsyn();
        execStatReset();
        pathstr := Settings.getModelicaPath(Testsuite.isRunning());
        strs := List.map(cvars, ValuesUtil.extractValueString);
        /* If the user requests a custom version to parse as, set it up */
        oldLanguageStd := Config.getLanguageStandard();
        b1 := not stringEq(str,"");
        if b1 then
          Config.setLanguageStandard(Config.versionStringToStd(str));
        end if;
        (p,b) := loadModel({(path,"call to loadModel",strs,false)},pathstr,p,true,b,true,requireExactVersion,false);
        if b1 then
          Config.setLanguageStandard(oldLanguageStd);
        end if;
        Print.clearBuf();
        SymbolTable.setAbsyn(p);
        execStat("loadModel("+AbsynUtil.pathString(path)+")");
        outCache := FCore.emptyCache();
      then
        Values.BOOL(b);

    case ("loadModel",Values.CODE(Absyn.C_TYPENAME(path))::_)
      equation
        pathstr = AbsynUtil.pathString(path);
        Error.addMessage(Error.LOAD_MODEL_ERROR, {pathstr});
      then
        Values.BOOL(false);

    case ("loadFile",Values.STRING(name)::Values.STRING(encoding)::Values.BOOL(b)::Values.BOOL(b1)::Values.BOOL(requireExactVersion)::_)
      algorithm
        execStatReset();
        name := Testsuite.friendlyPath(name);
        newp := loadFile(name, encoding, SymbolTable.getAbsyn(), b, b1, requireExactVersion);
        execStat("loadFile("+name+")");
        SymbolTable.setAbsyn(newp);
        outCache := FCore.emptyCache();
      then
        Values.BOOL(true);

    case ("loadFile",_)
      then Values.BOOL(false);

    case ("loadFiles",Values.ARRAY(valueLst=vals)::Values.STRING(encoding)::Values.INTEGER(i)::Values.BOOL(b)::Values.BOOL(b1)::Values.BOOL(requireExactVersion)::_)
      algorithm
        strs := List.mapMap(vals,ValuesUtil.extractValueString,Testsuite.friendlyPath);
        newps := Parser.parallelParseFilesToProgramList(strs,encoding,numThreads=i);
        newp := List.fold(newps, function checkUsesAndUpdateProgram(checkUses=b, modelicaPath=Settings.getModelicaPath(Testsuite.isRunning()), notifyLoad=b1, requireExactVersion=requireExactVersion), SymbolTable.getAbsyn());
        SymbolTable.setAbsyn(newp);
        outCache := FCore.emptyCache();
      then
        Values.BOOL(true);

    case ("loadFiles",_)
      algorithm
        // System.GC_enable();
      then Values.BOOL(false);

    case ("parseEncryptedPackage",Values.STRING(filename)::Values.STRING(workdir)::_)
      algorithm
        vals := {}; // make sure is initialized, see #9250
        str := System.pwd();
        try
          0 := System.cd(System.dirname(filename));
          (b, filename) := unZipEncryptedPackageAndCheckFile(workdir, filename, false);
          if b then
            // clear the errors before!
            Error.clearMessages() "Clear messages";
            Print.clearErrorBuf() "Clear error buffer";
            filename := Testsuite.friendlyPath(filename);
            (paths) := Interactive.parseFile(filename, "UTF-8");
            vals := List.map(paths,ValuesUtil.makeCodeTypeName);
          end if;
        else
          b := false;
        end try;
        0 := System.cd(str);
      then
        ValuesUtil.makeArray(vals);

    case ("parseEncryptedPackage",_)
      then
        ValuesUtil.makeArray({});

    case ("loadEncryptedPackage",Values.STRING(filename)::Values.STRING(workdir)::Values.BOOL(bval)::Values.BOOL(b)::Values.BOOL(b1)::Values.BOOL(requireExactVersion)::_)
      algorithm
        str := System.pwd();
        try
          if System.cd(System.dirname(filename)) <> 0 then
            Error.addMessage(Error.FILE_NOT_FOUND_ERROR, {filename});
            fail();
          end if;

          (b, filename) := unZipEncryptedPackageAndCheckFile(workdir, filename, bval);
          if (b) then
            execStatReset();
            filename := Testsuite.friendlyPath(filename);
            p := SymbolTable.getAbsyn();
            newp := loadFile(filename, "UTF-8", p, b, b1, requireExactVersion);
            execStat("loadFile("+filename+")");
            SymbolTable.setAbsyn(newp);
          end if;
          outCache := FCore.emptyCache();
        else
          b := false;
        end try;
        0 := System.cd(str);
      then
        Values.BOOL(b);

    case ("loadEncryptedPackage",_)
      then Values.BOOL(false);

    case ("alarm",{Values.INTEGER(i)})
      then Values.INTEGER(System.alarm(i));

    case ("getClassNames", _) then getClassNames(args);

    case ("reloadClass",{Values.CODE(Absyn.C_TYPENAME(classpath)),Values.STRING(encoding)})
      algorithm
        Absyn.CLASS(info=SOURCEINFO(fileName=filename,lastModification=r2)) :=
          InteractiveUtil.getPathedClassInProgram(classpath, SymbolTable.getAbsyn());
        (true,_,r1,_) := System.stat(filename);
        if not realEq(r1, r2) then
          reloadClass(filename, encoding);
        end if;
      then
        Values.BOOL(true);

    case ("reloadClass",{Values.CODE(Absyn.C_TYPENAME(classpath)),_})
      algorithm
        failure(_ := InteractiveUtil.getPathedClassInProgram(classpath, SymbolTable.getAbsyn()));
        Error.addMessage(Error.LOAD_MODEL_ERROR, {AbsynUtil.pathString(classpath)});
      then
        Values.BOOL(false);

    case ("reloadClass",_)
      then Values.BOOL(false);

    case ("loadString",Values.STRING(str)::Values.STRING(name)::Values.STRING(encoding)::Values.BOOL(mergeAST)::_)
      algorithm
        str := if not (encoding == "UTF-8") then System.iconv(str, encoding, "UTF-8") else str;
        newp := Parser.parsestring(str,name);
        newp := InteractiveUtil.updateProgram(newp, SymbolTable.getAbsyn(), mergeAST);
        SymbolTable.setAbsyn(newp);
        outCache := FCore.emptyCache();
      then
        Values.BOOL(true);

    case ("loadString",_) then Values.BOOL(false);

    case ("help",{Values.STRING("")})
      then Values.STRING(FlagsUtil.printUsage());

    case ("help",{Values.STRING(str)})
      then Values.STRING(FlagsUtil.printHelp({str}));

    case ("getTimeStamp",{Values.CODE(Absyn.C_TYPENAME(classpath))})
      algorithm
        Absyn.CLASS(info=SOURCEINFO(lastModification=r)) :=
          InteractiveUtil.getPathedClassInProgram(classpath,SymbolTable.getAbsyn());
        str := System.ctime(r);
      then
        Values.TUPLE({Values.REAL(r),Values.STRING(str)});

    case ("getTimeStamp",_)
      then Values.TUPLE({Values.REAL(0.0),Values.STRING("")});

    case ("getClassRestriction",{Values.CODE(Absyn.C_TYPENAME(classpath))})
      algorithm
        str := Interactive.getClassRestriction(classpath, SymbolTable.getAbsyn());
      then
        Values.STRING(str);

    case ("classAnnotationExists",{Values.CODE(Absyn.C_TYPENAME(classpath)),Values.CODE(Absyn.C_TYPENAME(path))})
      algorithm
        b := Interactive.getNamedAnnotation(classpath, SymbolTable.getAbsyn(), path, SOME(false), isSome);
      then
        Values.BOOL(b);

    case ("getBooleanClassAnnotation",{Values.CODE(Absyn.C_TYPENAME(classpath)),Values.CODE(Absyn.C_TYPENAME(path))})
      algorithm
        Absyn.BOOL(b) := Interactive.getNamedAnnotation(classpath, SymbolTable.getAbsyn(), path, NONE(), Interactive.getAnnotationExp);
      then
        Values.BOOL(b);

    case ("getBooleanClassAnnotation",{Values.CODE(Absyn.C_TYPENAME(classpath)),Values.CODE(Absyn.C_TYPENAME(path))})
      algorithm
        Error.addMessage(Error.CLASS_ANNOTATION_DOES_NOT_EXIST,
          {AbsynUtil.pathString(path), AbsynUtil.pathString(classpath)});
      then
        fail();

    case ("strtok",{Values.STRING(str),Values.STRING(token)})
      algorithm
        strs := System.strtok(str, token);
      then
        ValuesUtil.makeStringArray(strs);

    case ("stringSplit",{Values.STRING(str),Values.STRING(token)})
      algorithm
        strs := Util.stringSplitAtChar(str, token);
      then
        ValuesUtil.makeStringArray(strs);

    case ("stringReplace",{Values.STRING(str1),Values.STRING(str2),Values.STRING(str3)})
      algorithm
        str := System.stringReplace(str1, str2, str3);
      then
        Values.STRING(str);

    /* Checks the installation of OpenModelica and tries to find common errors */
    case ("checkSettings",{}) then checkSettings();

    case ("echo",{v as Values.BOOL(bval)})
      algorithm
        Settings.setEcho(if bval then 1 else 0);
      then
        v;

    case ("numProcessors",{})
      then Values.INTEGER(Config.noProc());

    case ("runScript",{Values.STRING(str)})
      algorithm
        str := Testsuite.friendlyPath(str);
        res := Interactive.evaluate(Parser.parseexp(str), true);
      then
        Values.STRING(res);

    case ("runScript",_)
      then Values.STRING("Failed");

    case ("exit",{Values.INTEGER(i)})
      equation
        System.exit(i);
        /* Cannot reach here */
      then
        fail();

    case ("getMemorySize",{})
      then Values.REAL(System.getMemorySize());

    case ("getAllSubtypeOf",{
          Values.CODE(Absyn.C_TYPENAME(path)),
          Values.CODE(Absyn.C_TYPENAME(parentClass)),
          Values.BOOL(qualified),
          Values.BOOL(includePartial),
          Values.BOOL(sort)})
      algorithm
        paths := InteractiveUtil.getAllSubtypeOf(path, parentClass, SymbolTable.getAbsyn(), qualified, includePartial);
        paths := if sort then List.sort(paths, AbsynUtil.pathGe) else paths;
        vals := List.map(paths,ValuesUtil.makeCodeTypeName);
      then
        ValuesUtil.makeArray(vals);

    // check for OMSimulator API calls
    case (_,_)
      then CevalScriptOMSimulator.ceval(functionName, args);

    else
      algorithm
        (outCache,v) := CevalScriptBackend.cevalInteractiveFunctions3(outCache,env,functionName,args,msg);
      then
        v;

 end matchcontinue;
end cevalInteractiveFunctions2;

public function evalCodeTypeName
  input Values.Value val;
  input FCore.Graph env;
  output Values.Value res;
algorithm
  res := matchcontinue (val,env)
    local
      Absyn.Path path;
    case (Values.CODE(Absyn.C_TYPENAME(path as Absyn.IDENT(_) /* We only want to lookup idents in the symboltable; also speeds up e.g. simulate(Modelica.A.B.C) so we do not instantiate all classes */)),_)
      equation
        (_,_,_,DAE.VALBOUND(valBound=res as Values.CODE(A=Absyn.C_TYPENAME())),_,_,_,_,_) = Lookup.lookupVar(FCore.emptyCache(), env, ComponentReference.pathToCref(path));
      then res;
    else val;
  end matchcontinue;
end evalCodeTypeName;

protected function getVariableNames
  input list<GlobalScript.Variable> vars;
  input list<Values.Value> acc;
  output list<Values.Value> ovars;
algorithm
  ovars := match (vars,acc)
    local
      list<GlobalScript.Variable> vs;
      String p;
    case ({},_) then listReverse(acc);
    case (GlobalScript.IVAR(varIdent = "$echo") :: vs,_)
      then getVariableNames(vs,acc);
    case (GlobalScript.IVAR(varIdent = p) :: vs,_)
      then getVariableNames(vs,Values.CODE(Absyn.C_VARIABLENAME(Absyn.CREF_IDENT(p,{})))::acc);
  end match;
end getVariableNames;

public function getPackageVersion
  input Absyn.Path path;
  input Absyn.Program p;
  output String version = "";
protected
  Boolean evalParamAnn;
algorithm
  evalParamAnn := Config.getEvaluateParametersInAnnotations();
  Config.setEvaluateParametersInAnnotations(true);
  try
    Absyn.STRING(version) := Interactive.getNamedAnnotation(path, p, Absyn.IDENT("version"), SOME(Absyn.STRING("")), Interactive.getAnnotationExp);
  else
    version := "";
  end try;
  Config.setEvaluateParametersInAnnotations(evalParamAnn);
end getPackageVersion;

protected function errorToValue
  input ErrorTypes.TotalMessage err;
  output Values.Value val;
algorithm
  val := match err
    local
      Absyn.Path msgpath;
      Values.Value tyVal,severityVal,infoVal;
      list<Values.Value> values;
      Gettext.TranslatableContent message;
      String msg_str;
      Integer id;
      ErrorTypes.Severity severity;
      ErrorTypes.MessageType ty;
      SourceInfo info;
    case ErrorTypes.TOTALMESSAGE(ErrorTypes.MESSAGE(id,ty,severity,message),info)
      equation
        msg_str = Gettext.translateContent(message);
        msgpath = Absyn.FULLYQUALIFIED(Absyn.QUALIFIED("OpenModelica",Absyn.QUALIFIED("Scripting",Absyn.IDENT("ErrorMessage"))));
        tyVal = errorTypeToValue(ty);
        severityVal = errorLevelToValue(severity);
        infoVal = infoToValue(info);
        values = {infoVal,Values.STRING(msg_str),tyVal,severityVal,Values.INTEGER(id)};
      then Values.RECORD(msgpath,values,{"info","message","kind","level","id"},-1);
  end match;
end errorToValue;

protected function infoToValue
  input SourceInfo info;
  output Values.Value val;
algorithm
  val := match info
    local
      list<Values.Value> values;
      Absyn.Path infopath;
      Integer ls,cs,le,ce;
      String filename;
      Boolean readonly;
    case SOURCEINFO(filename,readonly,ls,cs,le,ce,_)
      equation
        infopath = Absyn.FULLYQUALIFIED(Absyn.QUALIFIED("OpenModelica",Absyn.QUALIFIED("Scripting",Absyn.IDENT("SourceInfo"))));
        values = {Values.STRING(filename),Values.BOOL(readonly),Values.INTEGER(ls),Values.INTEGER(cs),Values.INTEGER(le),Values.INTEGER(ce)};
      then Values.RECORD(infopath,values,{"filename","readonly","lineStart","columnStart","lineEnd","columnEnd"},-1);
  end match;
end infoToValue;

protected function makeErrorEnumLiteral
  input String enumName;
  input String enumField;
  input Integer index;
  output Values.Value val;
  annotation(__OpenModelica_EarlyInline=true);
algorithm
  val := Values.ENUM_LITERAL(Absyn.FULLYQUALIFIED(Absyn.QUALIFIED("OpenModelica",Absyn.QUALIFIED("Scripting",Absyn.QUALIFIED(enumName,Absyn.IDENT(enumField))))),index);
end makeErrorEnumLiteral;

protected function errorTypeToValue
  input ErrorTypes.MessageType ty;
  output Values.Value val;
algorithm
  val := match ty
    case ErrorTypes.SYNTAX() then makeErrorEnumLiteral("ErrorKind","syntax",1);
    case ErrorTypes.GRAMMAR() then makeErrorEnumLiteral("ErrorKind","grammar",2);
    case ErrorTypes.TRANSLATION() then makeErrorEnumLiteral("ErrorKind","translation",3);
    case ErrorTypes.SYMBOLIC() then makeErrorEnumLiteral("ErrorKind","symbolic",4);
    case ErrorTypes.SIMULATION() then makeErrorEnumLiteral("ErrorKind","runtime",5);
    case ErrorTypes.SCRIPTING() then makeErrorEnumLiteral("ErrorKind","scripting",6);
    else
      equation
        print("errorTypeToValue failed\n");
      then fail();
  end match;
end errorTypeToValue;

protected function errorLevelToValue
  input ErrorTypes.Severity severity;
  output Values.Value val;
algorithm
  val := match severity
    case ErrorTypes.INTERNAL() then makeErrorEnumLiteral("ErrorLevel","internal",1);
    case ErrorTypes.ERROR() then makeErrorEnumLiteral("ErrorLevel","error",2);
    case ErrorTypes.WARNING() then makeErrorEnumLiteral("ErrorLevel","warning",3);
    case ErrorTypes.NOTIFICATION() then makeErrorEnumLiteral("ErrorLevel","notification",4);
    else
      equation
        print("errorLevelToValue failed\n");
      then fail();
  end match;
end errorLevelToValue;

protected function generateFunctionName
"@author adrpo:
 generate the function name from a path."
  input Absyn.Path functionPath;
  output String functionName;
algorithm
  functionName := AbsynUtil.pathStringUnquoteReplaceDot(functionPath, "_");
end generateFunctionName;

protected function generateFunctionFileName
"@author adrpo:
 generate the function name from a path."
  input Absyn.Path functionPath;
  output String functionName;
protected
  String n1, n2;
algorithm
  functionName := AbsynUtil.pathStringUnquoteReplaceDot(functionPath, "_");

  if stringLength(functionName) > Global.maxFunctionFileLength then
    n1 := AbsynUtil.pathFirstIdent(functionPath);
    n2 := AbsynUtil.pathLastIdent(functionPath);
    functionName := System.unquoteIdentifier(n1 + "_" + n2);
    functionName := functionName + "_" + intString(tick());
  end if;
end generateFunctionFileName;

public function getFunctionDependencies
"returns all function dependencies as paths, also the main function and the function tree"
  input FCore.Cache cache;
  input Absyn.Path functionName;
  output DAE.Function mainFunction "the main function";
  output list<Absyn.Path> dependencies "the dependencies as paths";
  output DAE.FunctionTree funcs "the function tree";
algorithm
  funcs := FCore.getFunctionTree(cache);
  // First check if the main function exists... If it does not it might be an interactive function...
  mainFunction := DAEUtil.getNamedFunction(functionName, funcs);
  dependencies := SimCodeFunction.getCalledFunctionsInFunction(functionName,funcs);
end getFunctionDependencies;

public function collectDependencies
"collects all function dependencies, also the main function, uniontypes, metarecords"
  input FCore.Cache inCache;
  input FCore.Graph env;
  input Absyn.Path functionName;
  output FCore.Cache outCache;
  output DAE.Function mainFunction;
  output list<DAE.Function> dependencies;
  output list<DAE.Type> metarecordTypes;
protected
  list<Absyn.Path> uniontypePaths,paths;
  DAE.FunctionTree funcs;
algorithm
  (mainFunction, paths, funcs) := getFunctionDependencies(inCache, functionName);
  // The list of functions is not ordered, so we need to filter out the main function...
  dependencies := List.map1(paths, DAEUtil.getNamedFunction, funcs);
  dependencies := List.setDifference(dependencies, {mainFunction});
  uniontypePaths := DAEUtil.getUniontypePaths(dependencies,{});
  (outCache,metarecordTypes) := Lookup.lookupMetarecordsRecursive(inCache, env, uniontypePaths);
end collectDependencies;

public function cevalGenerateFunction "Generates code for a given function name."
  input FCore.Cache inCache;
  input FCore.Graph inEnv;
  input Absyn.Program program;
  input Absyn.Path inPath;
  output FCore.Cache outCache;
  output String functionName;
  output String functionFileName;
algorithm
  (outCache,functionName,functionFileName) := matchcontinue (inCache,inEnv,program,inPath)
    local
      String pathstr, fileName;
      FCore.Graph env;
      Absyn.Path path;
      FCore.Cache cache;
      DAE.Function mainFunction;
      list<DAE.Function> dependencies;
      list<DAE.Type> metarecordTypes;
      DAE.FunctionTree funcs;
    // template based translation
    case (cache, env, _, path) guard Flags.isSet(Flags.GEN) and (not Flags.isSet(Flags.GENERATE_CODE_CHEAT))
      algorithm
        (cache, mainFunction, dependencies, metarecordTypes) := collectDependencies(cache, env, path);
        pathstr  := generateFunctionName(path);
        fileName := generateFunctionFileName(path);
        funcs := FCore.getFunctionTree(cache);
        SimCodeFunction.translateFunctions(program, fileName, SOME(mainFunction), dependencies, metarecordTypes, {});
        compileModel(fileName, {});
      then
        (cache, pathstr, fileName);
    // Cheat if we want to generate code for Main.main
    // * Don't do dependency analysis of what functions to generate; just generate all of them
    // * Don't generate extra code for unreferenced MetaRecord types (for external functions)
    //   This could be an annotation instead anyway.
    // * Don't compile the generated files
    case (cache, _, _, path) guard Flags.isSet(Flags.GEN) and Flags.isSet(Flags.GENERATE_CODE_CHEAT)
      algorithm
        funcs := FCore.getFunctionTree(cache);
        // First check if the main function exists... If it does not it might be an interactive function...
        pathstr := generateFunctionName(path);
        fileName := generateFunctionFileName(path);
        // The list of functions is not ordered, so we need to filter out the main function...
        dependencies := DAEUtil.getFunctionList(funcs);
        SimCodeFunction.translateFunctions(program, fileName, NONE(), dependencies, {}, {});
      then
        (cache, pathstr, fileName);
    case (cache, env, _, path) guard Flags.isSet(Flags.GEN) and Flags.isSet(Flags.FAILTRACE)
      algorithm
        (cache,false) := Static.isExternalObjectFunction(cache,env,path);
        pathstr := generateFunctionName(path);
        fileName := generateFunctionFileName(path);
        Debug.trace("CevalScript.cevalGenerateFunction failed:\nfunction: " + pathstr + "\nfile: " + fileName + "\n");
      then
        fail();
  end matchcontinue;
end cevalGenerateFunction;

protected function matchQualifiedCalls
"Collects the packages used by the functions"
  input DAE.Exp inExp;
  input list<String> inAcc;
  output DAE.Exp outExp = inExp;
  output list<String> outAcc;
algorithm
  outAcc := match inExp
    local
      String name;

    case DAE.REDUCTION(reductionInfo = DAE.REDUCTIONINFO(path = Absyn.FULLYQUALIFIED(Absyn.QUALIFIED(name = name))))
      then List.consOnTrue(not listMember(name, inAcc), name, inAcc);

    case DAE.CALL(path = Absyn.FULLYQUALIFIED(Absyn.QUALIFIED(name = name)),
                  attr = DAE.CALL_ATTR(builtin = false))
      then List.consOnTrue(not listMember(name, inAcc), name, inAcc);

    case DAE.CREF(componentRef = DAE.CREF_QUAL(ident = name),
                  ty = DAE.T_FUNCTION_REFERENCE_FUNC(builtin = false))
      then List.consOnTrue(not listMember(name, inAcc), name, inAcc);

    case DAE.PARTEVALFUNCTION(path = Absyn.FULLYQUALIFIED(Absyn.QUALIFIED(name = name)))
      then List.consOnTrue(not listMember(name, inAcc), name, inAcc);

    else inAcc;
  end match;
end matchQualifiedCalls;

protected function instantiateDaeFunctions
  input FCore.Cache icache;
  input FCore.Graph ienv;
  input list<Absyn.Path> ipaths;
  output FCore.Cache outCache;
algorithm
  outCache := match (icache,ienv,ipaths)
    local
      Absyn.Path path;
      FCore.Cache cache; FCore.Graph env;
      list<Absyn.Path> paths;
    case (cache,_,{}) then cache;
    case (cache,env,path::paths)
      equation
        // print("force inst: " + AbsynUtil.pathString(path));
        (cache,Util.SUCCESS()) = Static.instantiateDaeFunctionForceInst(cache,env,path,false,NONE(),true);
        // print(" ok\n");
        cache = instantiateDaeFunctions(cache,env,paths);
      then cache;
  end match;
end instantiateDaeFunctions;

function generateFunctions
  input FCore.Cache icache;
  input FCore.Graph ienv;
  input Absyn.Program p;
  input SCode.Program fullScodeProgram;
  input list<SCode.Element> isp;
  input Boolean cleanCache;
  output FCore.Cache cache;
  output FCore.Graph env;
algorithm
  (cache,env) := match (icache,ienv,p,isp,cleanCache)
    local
      String name;
      list<String> names,dependencies;
      list<Absyn.Path> paths;
      list<SCode.Element> elementLst;
      DAE.FunctionTree funcs;
      list<DAE.Function> d;
      list<tuple<String,list<String>>> acc;
      list<SCode.Element> sp;
      String file,nameHeader,str;
      Integer n;
      SourceInfo info;
      SCode.Element cl;
      SCode.Restriction restr;

    case (cache,env,_,{},_) then (cache,env);
    case (cache,env,_,(cl as SCode.CLASS(name=name,encapsulatedPrefix=SCode.ENCAPSULATED(),restriction=restr,info=info))::sp,_)
      algorithm
        _ := match restr
          case SCode.R_PACKAGE() then ();
          case SCode.R_UNIONTYPE() then ();
          else
            algorithm
              Error.addSourceMessage(Error.INTERNAL_ERROR, {"Only package and uniontype is supported as top-level classes in OpenModelica."}, info);
            then fail();
        end match;
        (cache,env) := generateFunctions2(cache,env,p,fullScodeProgram,cl,name,info,cleanCache);
        (cache,env) := generateFunctions(cache,env,p,fullScodeProgram,sp,cleanCache);
      then (cache,env);
    case (_,_,_,SCode.CLASS(encapsulatedPrefix=SCode.NOT_ENCAPSULATED(),name=name,info=info as SOURCEINFO(fileName=file))::_,_)
      equation
        (n,_) = System.regex(file, "ModelicaBuiltin.mo$", 1, false, false);
        Error.assertion(n > 0, "Not an encapsulated class (required for separate compilation): " + name, info);
      then fail();
  end match;
end generateFunctions;

function generateFunctions2
  input FCore.Cache icache;
  input FCore.Graph ienv;
  input Absyn.Program p;
  input SCode.Program sp;
  input SCode.Element cl;
  input String name;
  input SourceInfo info;
  input Boolean cleanCache;
  output FCore.Cache cache;
  output FCore.Graph env;
algorithm
  (cache,env) := matchcontinue (icache,ienv,p,cl,name,info,cleanCache)
    local
      list<String> names,dependencies,strs;
      list<Absyn.Path> paths, pathsMetarecord;
      DAE.FunctionTree funcs;
      list<DAE.Function> d;
      list<tuple<String,list<String>>> acc;
      String file,nameHeader,str;
      Integer n;
      FCore.Graph env2;
      FCore.Ref ref;
      FCore.Cache lookupCache;
      FCore.Children children;
      Absyn.Path path;
      list<SCode.Element> elements;
      list<DAE.Type> metarecords;
      DAE.Type t;

    case (cache,env,_,_,_,SOURCEINFO(fileName=file),_)
      equation
        (1,_) = System.regex(file, "ModelicaBuiltin.mo$", 1, false, false);
      then (cache,env);

    case (cache,env,_,_,_,_,_)
      algorithm
        cache := if cleanCache then FCore.emptyCache() else cache;

        if SCodeUtil.isPartial(cl) then
          paths := {};
          pathsMetarecord := {};
        else
          path := Absyn.FULLYQUALIFIED(Absyn.IDENT(name));
          elements := getNonPartialElementsForInstantiatedClass(sp, cl, path);
          (paths, pathsMetarecord) := List.fold22(elements, findFunctionsToCompile, path, sp, {}, {});
        end if;

        metarecords := {};
        for mr in pathsMetarecord loop
          (cache,t) := Lookup.lookupType(cache, env, mr, SOME(info));
          metarecords := t::metarecords;
        end for;

        cache := instantiateDaeFunctions(cache, env, paths);
        funcs := FCore.getFunctionTree(cache);
        d := List.map2(paths, DAEUtil.getNamedFunctionWithError, funcs, info);
        (_,(_,dependencies)) := DAEUtil.traverseDAEFunctions(d,Expression.traverseSubexpressionsHelper,(matchQualifiedCalls,{}));
        // print(name + " has dependencies: " + stringDelimitList(dependencies,",") + "\n");
        dependencies := List.sort(dependencies,Util.strcmpBool);
        dependencies := List.map1(dependencies,stringAppend,".h");
        nameHeader := name + ".h";
        strs := List.map1r(nameHeader::dependencies, stringAppend, "$(GEN_DIR)");
        System.writeFile(name + ".deps", "$(GEN_DIR)" + name + ".o: $(GEN_DIR)" + name + ".c" + " " + stringDelimitList(strs," "));
        dependencies := List.map1(dependencies,stringAppend,"\"");
        dependencies := List.map1r(dependencies,stringAppend,"#include \"");
        SimCodeFunction.translateFunctions(p, name, NONE(), d, {}, dependencies);
        str := Tpl.tplString(Unparsing.programExternalHeaderFromTypes, metarecords);
        System.writeFile(name + "_records.c","#include <meta/meta_modelica.h>\n" + str);
        cache := if cleanCache then icache else cache;
      then (cache,env);
    else
      equation
        Error.addSourceMessage(Error.SEPARATE_COMPILATION_PACKAGE_FAILED,{name},info);
      then fail();
  end matchcontinue;
end generateFunctions2;

function findFunctionsToCompile
  input SCode.Element elt;
  input Absyn.Path pathPrefix;
  input SCode.Program sp;
  input list<Absyn.Path> acc;
  input list<Absyn.Path> accMetarecord;
  output list<Absyn.Path> paths;
  output list<Absyn.Path> pathsMetarecord;
protected
  String name;
  Absyn.Path path;
  list<SCode.Element> elements;
algorithm
  SCode.CLASS(name=name) := elt;
  path := AbsynUtil.joinPaths(pathPrefix, Absyn.IDENT(name));
  paths := if SCodeUtil.isFunction(elt) then path::acc else acc;
  pathsMetarecord := match elt
    case SCode.CLASS(restriction=SCode.R_METARECORD()) then path::accMetarecord;
    else accMetarecord;
  end match;
  elements := getNonPartialElementsForInstantiatedClass(sp, elt, path);
  (paths,pathsMetarecord) := List.fold22(elements, findFunctionsToCompile, path, sp, paths, pathsMetarecord);
end findFunctionsToCompile;

function getNonPartialElementsForInstantiatedClass "Gets the non-partial elements returned by instantiating the given path"
  input SCode.Program sp;
  input SCode.Element cl;
  input Absyn.Path p;
  output list<SCode.Element> elts;
protected
  FCore.Graph env;
  SCode.Element elt;
  Boolean skip;
  list<SCode.Element> eltsTmp;
algorithm
  skip := match cl
    case SCode.CLASS(classDef=SCode.CLASS_EXTENDS()) then false;
    case SCode.CLASS(classDef=SCode.PARTS(elementLst=eltsTmp)) then not List.exist(eltsTmp, SCodeUtil.isElementExtendsOrClassExtends);
    else true;
  end match;
  if not skip then
  try
    ErrorExt.setCheckpoint("getNonPartialElementsForInstantiatedClass");
    (, env) := Inst.instantiateClass(FCore.emptyCache(), InnerOuter.emptyInstHierarchy, sp, AbsynUtil.makeNotFullyQualified(p), doSCodeDep=false);
    elts := FCore.RefTree.fold(FNode.children(FNode.fromRef(FGraph.lastScopeRef(env))),
      addNonPartialClassRef, {});
    ErrorExt.rollBack("getNonPartialElementsForInstantiatedClass");
    return;
  else
  end try;
  ErrorExt.rollBack("getNonPartialElementsForInstantiatedClass");
  end if;
  // Failed to instantiate the class; perhaps due to being a function
  // that cannot be instantiated using model restrictions.
  elts := match cl
    case SCode.CLASS(classDef=SCode.PARTS(elementLst=elts)) then list(e for e guard (not SCodeUtil.isPartial(e)) and SCodeUtil.isClass(e) in elts);
    else {};
  end match;
end getNonPartialElementsForInstantiatedClass;

protected function addNonPartialClassRef
  input FCore.Name name;
  input FCore.Ref ref;
  input list<SCode.Element> accum;
  output list<SCode.Element> classes;
protected
  SCode.Element e;
algorithm
  classes := match FNode.fromRef(ref)
    case FCore.N(data = FCore.CL(e = e as SCode.CLASS(partialPrefix = SCode.NOT_PARTIAL())))
      then e :: accum;

    else accum;
  end match;
end addNonPartialClassRef;

public function cevalCallFunction "This function evaluates CALL expressions, i.e. function calls.
  They are currently evaluated by generating code for the function and
  then dynamicly load the function and call it."
  input FCore.Cache inCache;
  input FCore.Graph inEnv;
  input DAE.Exp inExp;
  input list<Values.Value> inValuesValueLst;
  input Boolean impl;
  input Absyn.Msg inMsg;
  input Integer numIter;
  output FCore.Cache outCache;
  output Values.Value outValue;
algorithm
  (outCache,outValue) := matchcontinue (inCache,inEnv,inExp,inValuesValueLst,impl,inMsg,numIter)
    local
      Values.Value newval;
      FCore.Graph env;
      DAE.Exp e;
      Absyn.Path funcpath;
      list<DAE.Exp> expl;
      list<Values.Value> vallst, pubVallst, proVallst;
      Absyn.Msg msg;
      FCore.Cache cache;
      Absyn.Path complexName;
      list<DAE.Var> pubVarLst, proVarLst, varLst;
      list<String> pubVarNames, proVarNames, varNames;
      DAE.Type ty;
      SourceInfo info;
      String str;
      Boolean bIsCompleteFunction;

    // External functions that are "known" should be evaluated without compilation, e.g. all math functions
    case (cache,env,(DAE.CALL(path = funcpath)),vallst,_,msg,_)
      equation
        (cache,newval) = Ceval.cevalKnownExternalFuncs(cache,env, funcpath, vallst, msg);
      then
        (cache,newval);

    // This case prevents the constructor call of external objects of being evaluated
    case (cache,env,(DAE.CALL(path = funcpath)),_,_,msg,_)
      equation
        true = FGraph.isNotEmpty(env);
        cevalIsExternalObjectConstructor(cache,funcpath,env,msg);
      then
        fail();

    // Record constructors
    case(cache,env,(DAE.CALL(path = funcpath,attr = DAE.CALL_ATTR(ty = DAE.T_COMPLEX(complexClassType = ClassInf.RECORD(complexName), varLst=varLst)))),pubVallst,_,msg,_)
      equation
        if Flags.isSet(Flags.DYN_LOAD) then
          Debug.traceln("CALL: record constructor: func: " + AbsynUtil.pathString(funcpath) + " type path: " + AbsynUtil.pathString(complexName));
        end if;
        true = AbsynUtil.pathEqual(funcpath,complexName);
        (pubVarLst,proVarLst) = List.splitOnTrue(varLst,Types.isPublicVar);
        expl = List.map1(proVarLst, Types.getBindingExp, funcpath);
        (cache,proVallst) = Ceval.cevalList(cache, env, expl, impl, msg, numIter);
        pubVarNames = List.map(pubVarLst,Expression.varName);
        proVarNames = List.map(proVarLst,Expression.varName);
        varNames = listAppend(pubVarNames, proVarNames);
        vallst = listAppend(pubVallst, proVallst);
        // fprintln(Flags.DYN_LOAD, "CALL: record constructor: [success] func: " + AbsynUtil.pathString(funcpath));
      then
        (cache,Values.RECORD(funcpath,vallst,varNames,-1));

    // evaluate or generate non-partial and non-replaceable functions
    case (cache,env, DAE.CALL(path = funcpath, attr = DAE.CALL_ATTR(ty = ty, builtin = false)), _, _, msg, _)
      equation
        failure(cevalIsExternalObjectConstructor(cache, funcpath, env, msg));
        if Flags.isSet(Flags.DYN_LOAD) then
          Debug.traceln("CALL: try to evaluate or generate function: " + AbsynUtil.pathString(funcpath));
        end if;

        bIsCompleteFunction = isCompleteFunction(cache, env, funcpath);
        false = Types.hasMetaArray(ty);

        if Flags.isSet(Flags.DYN_LOAD) then
          Debug.traceln("CALL: is complete function: " + AbsynUtil.pathString(funcpath) + " " +  (if bIsCompleteFunction then "[true]" else "[false]"));
        end if;
        (cache, newval) = cevalCallFunctionEvaluateOrGenerate(inCache,inEnv,inExp,inValuesValueLst,impl,inMsg,bIsCompleteFunction);

        // Debug.fprintln(Flags.DYN_LOAD, "CALL: constant evaluation success: " + AbsynUtil.pathString(funcpath));
      then
        (cache, newval);

    // partial and replaceable functions should not be evaluated!
    case (cache,env, DAE.CALL(path = funcpath, attr = DAE.CALL_ATTR( builtin = false)), _, _, msg, _)
      equation
        failure(cevalIsExternalObjectConstructor(cache, funcpath, env, msg));
        false = isCompleteFunction(cache, env, funcpath);

        if Flags.isSet(Flags.DYN_LOAD) then
          Debug.traceln("CALL: constant evaluation failed (not complete function): " + AbsynUtil.pathString(funcpath));
        end if;
      then
        fail();
/*
    case (cache,env, DAE.CALL(path = funcpath, attr = DAE.CALL_ATTR(ty = ty, builtin = false)), _, _, msg as Absyn.MSG(info), _)
      equation
        failure(cevalIsExternalObjectConstructor(cache, funcpath, env, msg));
        true = isCompleteFunction(cache, env, funcpath);
        true = Types.hasMetaArray(ty);
        str = ExpressionDump.printExpStr(inExp);
        Error.addSourceMessage(Error.FUNCTION_RETURNS_META_ARRAY, {str}, info);
      then fail();
*/
    else fail();

  end matchcontinue;
end cevalCallFunction;

protected

function cevalCallFunctionEvaluateOrGenerate
"This function evaluates CALL expressions, i.e. function calls.
  They are currently evaluated by generating code for the function and
  then dynamicly load the function and call it."
  input FCore.Cache inCache;
  input FCore.Graph inEnv;
  input DAE.Exp inExp;
  input list<Values.Value> inValuesValueLst;
  input Boolean impl;
  input Absyn.Msg inMsg;
  input Boolean bIsCompleteFunction;
  output FCore.Cache outCache;
  output Values.Value outValue;
protected
  Integer numCheckpoints;
algorithm
  // Only add a stack overflow checkpoint for the top-most cevalCallFunctionEvaluateOrGenerate
  if isNone(getGlobalRoot(Global.stackoverFlowIndex)) then
    setGlobalRoot(Global.stackoverFlowIndex, SOME(1));
    numCheckpoints:=ErrorExt.getNumCheckpoints();
    try
      StackOverflow.clearStacktraceMessages();
      (outCache,outValue) := cevalCallFunctionEvaluateOrGenerate2(inCache,inEnv,inExp,inValuesValueLst,impl,inMsg,bIsCompleteFunction);
    else
      setGlobalRoot(Global.stackoverFlowIndex, NONE());
      ErrorExt.rollbackNumCheckpoints(ErrorExt.getNumCheckpoints()-numCheckpoints);
      Error.addInternalError("Stack overflow when evaluating function call: "+ExpressionDump.printExpStr(inExp)+"...\n"+stringDelimitList(StackOverflow.readableStacktraceMessages(), "\n"), match inMsg local SourceInfo info; case Absyn.MSG(info) then info; else sourceInfo(); end match);
      /* Do not fail or we can loop too much */
      StackOverflow.clearStacktraceMessages();
      outCache := inCache;
      outValue := Values.META_FAIL();
    end try annotation(__OpenModelica_stackOverflowCheckpoint=true);
    setGlobalRoot(Global.stackoverFlowIndex, NONE());
  else
    (outCache,outValue) := cevalCallFunctionEvaluateOrGenerate2(inCache,inEnv,inExp,inValuesValueLst,impl,inMsg,bIsCompleteFunction);
  end if;
end cevalCallFunctionEvaluateOrGenerate;

function cevalCallFunctionEvaluateOrGenerate2
"This function evaluates CALL expressions, i.e. function calls.
  They are currently evaluated by generating code for the function and
  then dynamicly load the function and call it."
  input FCore.Cache inCache;
  input FCore.Graph inEnv;
  input DAE.Exp inExp;
  input list<Values.Value> inValuesValueLst;
  input Boolean impl;
  input Absyn.Msg inMsg;
  input Boolean bIsCompleteFunction;
  output FCore.Cache outCache;
  output Values.Value outValue;
algorithm
  (outCache,outValue) := matchcontinue (inCache,inEnv,inExp,inValuesValueLst,impl,inMsg,bIsCompleteFunction)
    local
      Values.Value newval;
      FCore.Graph env;
      DAE.Exp e;
      Absyn.Path funcpath;
      list<DAE.Exp> expl;
      Boolean  print_debug;
      list<Values.Value> vallst;
      Absyn.Msg msg;
      FCore.Cache cache;
      Absyn.Program p;
      Integer libHandle, funcHandle;
      String fNew,fOld;
      Real buildTime, edit, build;
      Option<list<SCode.Element>> a;
      list<GlobalScript.Variable> c;
      String funcstr,f,fileName;
      String name;
      Boolean ppref, fpref, epref;
      Absyn.ClassDef    body;
      SourceInfo        info;
      Absyn.Within      w;
      list<Absyn.Path> functionDependencies;
      SCode.Element sc;
      SCode.ClassDef cdef;
      String error_Str;
      DAE.Function func;
      SCode.Restriction res;
      Absyn.FunctionRestriction funcRest;
      DAE.Type ty;

    // try function interpretation
    case (cache,env, DAE.CALL(path = funcpath, attr = DAE.CALL_ATTR(builtin = false)), vallst, _, msg, _)
      algorithm
        true := Flags.isSet(Flags.EVAL_FUNC);
        failure(cevalIsExternalObjectConstructor(cache, funcpath, env, msg));
        // bcall1(Flags.isSet(Flags.DYN_LOAD), print,"[dynload]: try constant evaluation: " + AbsynUtil.pathString(funcpath) + "\n");

        try
          func := FCore.getCachedInstFunc(cache, funcpath);
        else
          (cache,
           sc as SCode.CLASS(partialPrefix = SCode.NOT_PARTIAL()),
           env) := Lookup.lookupClass(cache, env, funcpath);
          isCevaluableFunction(sc);
          (cache, env, _) := InstFunction.implicitFunctionInstantiation(
            cache,
            env,
            InnerOuter.emptyInstHierarchy,
            DAE.NOMOD(),
            DAE.NOPRE(),
            sc,
            {});
          func := FCore.getCachedInstFunc(cache, funcpath);
        end try;

        (cache, newval) := CevalFunction.evaluate(cache, env, func, vallst);
        // bcall1(Flags.isSet(Flags.DYN_LOAD), print, "[dynload]: constant evaluation SUCCESS: " + AbsynUtil.pathString(funcpath) + "\n");
      then
        (cache, newval);

    // not in CF list, we have a symbol table, generate function and update symtab
    case (cache,env,(DAE.CALL(path = funcpath,attr = DAE.CALL_ATTR( builtin = false))),vallst,_, msg, _)
      guard (bIsCompleteFunction and Flags.isSet(Flags.GEN)) // yeha! we have a symboltable!
      algorithm
        failure(cevalIsExternalObjectConstructor(cache,funcpath,env,msg));

        if Flags.isSet(Flags.DYN_LOAD) then
          print("[dynload]: [SOME SYMTAB] not in in CF list: " + AbsynUtil.pathString(funcpath) + "\n");
        end if;

        p := SymbolTable.getAbsyn();
        // now is safe to generate code
        (cache, funcstr, fileName) := cevalGenerateFunction(cache, env, p, funcpath);
        print_debug := Flags.isSet(Flags.DYN_LOAD);
        libHandle := System.loadLibrary(fileName + Autoconf.dllExt, relativePath = true, printDebug = print_debug);
        funcHandle := System.lookupFunction(libHandle, stringAppend("in_", funcstr));
        execStatReset();
        newval := DynLoad.executeFunction(funcHandle, vallst, print_debug);
        execStat("executeFunction("+AbsynUtil.pathString(funcpath)+")");

        System.freeLibrary(libHandle, print_debug);
        // update the build time in the class!
        Absyn.CLASS(restriction=Absyn.R_FUNCTION(_),info=info) := InteractiveUtil.getPathedClassInProgram(funcpath, p);

        w := InteractiveUtil.buildWithin(funcpath);

        if Flags.isSet(Flags.DYN_LOAD) then
          print("[dynload]: Updating build time for function path: " + AbsynUtil.pathString(funcpath) + " within: " + Dump.unparseWithin(w) + "\n");
        end if;

        // p = Interactive.updateProgram(Absyn.PROGRAM({Absyn.CLASS(name,ppref,fpref,epref,Absyn.R_FUNCTION(funcRest),body,info)},w,ts), p);
        _ := AbsynUtil.getFileNameFromInfo(info);

        if Flags.isSet(Flags.DYN_LOAD) then
          print("[dynload]: [SOME SYMTAB] not in in CF list [finished]: " +
          AbsynUtil.pathString(funcpath) + "\n");
        end if;
      then
        (cache,newval);

    case (_,_,(DAE.CALL(path = funcpath)),_,_,_,_)
      equation
        if Flags.isSet(Flags.DYN_LOAD) then
          print("[dynload]: FAILED to constant evaluate function: " + AbsynUtil.pathString(funcpath) + "\n");
        end if;
        //TODO: readd this when testsuite is okay.
        //Error.addMessage(Error.FAILED_TO_EVALUATE_FUNCTION, {error_Str});
        false = Flags.isSet(Flags.GEN);
        true = Flags.isSet(Flags.FAILTRACE);
        Debug.trace("- codegeneration is turned off. switch \"nogen\" flag off\n");
      then
        fail();

  end matchcontinue;
end cevalCallFunctionEvaluateOrGenerate2;

function cevalIsExternalObjectConstructor
  input FCore.Cache cache;
  input Absyn.Path funcpath;
  input FCore.Graph env;
  input Absyn.Msg msg;
protected
  Absyn.Path funcpath2;
  DAE.Type tp;
  Option<SourceInfo> info;
algorithm
  _ := match(cache, funcpath, env, msg)
    case (_, _, FCore.EG(_), Absyn.NO_MSG()) then fail();
    case (_, _, _, Absyn.NO_MSG())
      equation
        (funcpath2, Absyn.IDENT("constructor")) = AbsynUtil.splitQualAndIdentPath(funcpath);
        info = if valueEq(msg, Absyn.NO_MSG()) then NONE() else SOME(AbsynUtil.dummyInfo);
        (_, tp, _) = Lookup.lookupType(cache, env, funcpath2, info);
        Types.externalObjectConstructorType(tp);
      then
        ();
  end match;
end cevalIsExternalObjectConstructor;

protected function checkLibraryUsage
  input String inLibrary;
  input Absyn.Exp inExp;
  output Boolean isUsed;
algorithm
  isUsed := match(inLibrary, inExp)
    local
      String s;
      list<Absyn.Exp> exps;

    case (_, Absyn.STRING(s)) then stringEq(s, inLibrary);
    case (_, Absyn.ARRAY(exps))
      then List.isMemberOnTrue(inLibrary, exps, checkLibraryUsage);
  end match;
end checkLibraryUsage;

function isCevaluableFunction
  "Checks if an element is a function or external function that can be evaluated
  by CevalFunction."
  input SCode.Element inElement;
algorithm
  _ := match(inElement)
    local
      String fid;
      SCode.Mod mod;
      Absyn.Exp lib;

    //only some external functions.
    case (SCode.CLASS(restriction = SCode.R_FUNCTION(SCode.FR_EXTERNAL_FUNCTION(_)),
      classDef = SCode.PARTS(externalDecl = SOME(SCode.EXTERNALDECL(
        funcName = SOME(fid),
        annotation_ = SOME(SCode.ANNOTATION(mod)))))))
      equation
        SCode.MOD(binding = SOME(lib)) = Mod.getUnelabedSubMod(mod, "Library");
        true = checkLibraryUsage("Lapack", lib) or checkLibraryUsage("lapack", lib);
        isCevaluableFunction2(fid);
      then
        ();

    // All other functions can be evaluated.
    case (SCode.CLASS(restriction = SCode.R_FUNCTION(_))) then ();

  end match;
end isCevaluableFunction;

function isCevaluableFunction2
  "Checks if a function name belongs to a known external function that we can
  constant evaluate."
  input String inFuncName;
algorithm
  _ := match(inFuncName)
    local
      // Lapack functions.
      case "dgbsv" then ();
      case "dgeev" then ();
      case "dgegv" then ();
      case "dgels" then ();
      case "dgelsx" then ();
      case "dgelsy" then ();
      case "dgeqpf" then ();
      case "dgesv" then ();
      case "dgesvd" then ();
      case "dgetrf" then ();
      case "dgetri" then ();
      case "dgetrs" then ();
      case "dgglse" then ();
      case "dgtsv" then ();
      case "dorgqr" then ();
  end match;
end isCevaluableFunction2;

function isSimpleAPIFunction
  input DAE.Type ty;
  output Boolean b;
algorithm
  b := match ty
    case DAE.T_FUNCTION(functionAttributes=DAE.FUNCTION_ATTRIBUTES(isBuiltin=DAE.FUNCTION_BUILTIN())) then
      isSimpleAPIFunctionArg(ty.funcResultType) and
      min(match fa case DAE.FUNCARG() then isSimpleAPIFunctionArg(fa.ty); end match for fa in ty.funcArg);
    else false;
  end match;
end isSimpleAPIFunction;

function isSimpleAPIFunctionArg
  input DAE.Type ty;
  output Boolean b;
algorithm
  b := match ty
    case DAE.T_INTEGER() then true;
    case DAE.T_REAL() then true;
    case DAE.T_BOOL() then true;
    case DAE.T_STRING() then true;
    case DAE.T_NORETCALL() then true;
    case DAE.T_ARRAY() then isSimpleAPIFunctionArg(ty.ty);
    case DAE.T_CODE(ty=DAE.C_TYPENAME()) then true;
    case DAE.T_TUPLE() then min(isSimpleAPIFunctionArg(t) for t in ty.types);
    else false;
  end match;
end isSimpleAPIFunctionArg;

function verifyInterfaceType
  input SCode.Element elt;
  input list<String> expected;
algorithm
  _ := matchcontinue (elt,expected)
    local
      String str,name;
      SCode.Annotation ann;
      SourceInfo info;
    case (SCode.CLASS(restriction=SCode.R_METARECORD(moved=true)),_) then ();
    case (SCode.CLASS(cmt=SCode.COMMENT(annotation_=SOME(ann))),name::_)
      algorithm
        SCode.MOD(binding = SOME(Absyn.STRING(str)), info = info) :=
          SCodeUtil.lookupAnnotation(ann, "__OpenModelica_Interface");
        Error.assertionOrAddSourceMessage(listMember(str, expected), Error.MISMATCHING_INTERFACE_TYPE, {str,name}, info);
      then ();
    else
      equation
        print(SCodeDump.unparseElementStr(elt)+"\n");
        Error.addSourceMessage(Error.MISSING_INTERFACE_TYPE,{},SCodeUtil.elementInfo(elt));
      then fail();
  end matchcontinue;
end verifyInterfaceType;

function getInterfaceType
  input SCode.Element elt;
  input list<tuple<String,list<String>>> assoc;
  output list<String> it;
algorithm
  it := matchcontinue (elt,assoc)
    local
      String name;
      SCode.Annotation ann;
      String str;
      SourceInfo info;
    case (SCode.CLASS(cmt=SCode.COMMENT(annotation_=SOME(ann))),_)
      equation
        SOME(Absyn.STRING(str)) = SCodeUtil.lookupAnnotationBinding(ann,"__OpenModelica_Interface");
        it = Util.assoc(str,assoc);
      then it;
    else
      equation
        Error.addSourceMessage(Error.MISSING_INTERFACE_TYPE,{},SCodeUtil.elementInfo(elt));
      then fail();
  end matchcontinue;
end getInterfaceType;

function getInterfaceTypeAssocElt
  input Values.Value val;
  input SourceInfo info;
  output tuple<String,list<String>> assoc;
algorithm
  assoc := match (val,info)
    local
      String str;
      list<String> strs;
      list<Values.Value> vals;
    case (Values.ARRAY(valueLst=Values.STRING("")::_),_)
      equation
        Error.addSourceMessage(Error.MISSING_INTERFACE_TYPE,{},info);
      then fail();
    case (Values.ARRAY(valueLst=Values.STRING(str)::vals),_)
      equation
        strs = List.select(List.map(vals,ValuesUtil.extractValueString), Util.isNotEmptyString);
      then ((str,str::strs));
  end match;
end getInterfaceTypeAssocElt;

protected function buildDependencyGraph
  input String name;
  input SCode.Program sp;
  output list<String> edges;
algorithm
  edges := match (name,sp)
    local
      list<SCode.Element> elts;
    case (_,_)
      equation
        SCode.CLASS(classDef=SCode.PARTS(elementLst=elts)) = List.getMemberOnTrue(name, sp, SCodeUtil.isClassNamed);
        (_,_,_,elts,_) = SCodeUtil.partitionElements(elts);
      then List.map(elts, importDepenency);
  end match;
end buildDependencyGraph;

protected function buildDependencyGraphPublicImports
  input String name;
  input SCode.Program sp;
  output list<String> edges;
algorithm
  edges := match (name,sp)
    local
      list<SCode.Element> elts;
    case (_,_)
      equation
        SCode.CLASS(classDef=SCode.PARTS(elementLst=elts)) = List.getMemberOnTrue(name, sp, SCodeUtil.isClassNamed);
        elts = List.select(elts,SCodeUtil.elementIsPublicImport);
      then List.map(elts, importDepenency);
  end match;
end buildDependencyGraphPublicImports;

protected function buildTransitiveDependencyGraph
  input String name;
  input list<tuple<String,list<String>>> oldgraph;
  output list<String> edges;
algorithm
  edges := matchcontinue (name,oldgraph)
    local
      String str;
    case (_,_) then List.setDifference(Graph.allReachableNodes(({name},{}),oldgraph,stringEq),{name});
    else
      equation
        str = "CevalScript.buildTransitiveDependencyGraph failed: " + name;
        Error.addMessage(Error.INTERNAL_ERROR, {str});
      then fail();
  end matchcontinue;
end buildTransitiveDependencyGraph;

protected function importDepenency
  input SCode.Element simp;
  output String name;
algorithm
  name := match simp
    local
      Absyn.Import imp;
      SourceInfo info;
      String str;
      Absyn.Path path;
    case SCode.IMPORT(imp=Absyn.NAMED_IMPORT(path=path)) then AbsynUtil.pathFirstIdent(path);
    case SCode.IMPORT(imp=Absyn.NAMED_IMPORT(path=path)) then AbsynUtil.pathFirstIdent(path);
    case SCode.IMPORT(imp=Absyn.QUAL_IMPORT(path=path)) then AbsynUtil.pathFirstIdent(path);
    case SCode.IMPORT(imp=Absyn.UNQUAL_IMPORT(path=path)) then AbsynUtil.pathFirstIdent(path);
    case SCode.IMPORT(imp=Absyn.GROUP_IMPORT(prefix=path)) then AbsynUtil.pathFirstIdent(path);
    case SCode.IMPORT(imp=imp,info=info)
      equation
        str = "CevalScript.importDepenency could not handle:" + Dump.unparseImportStr(imp);
        Error.addSourceMessage(Error.INTERNAL_ERROR,{str},info);
      then fail();
  end match;
end importDepenency;

protected function compareNumberOfDependencies
  input tuple<String,list<String>> node1;
  input tuple<String,list<String>> node2;
  output Boolean cmp;
protected
  list<String> deps1,deps2;
algorithm
  (_,deps1) := node1;
  (_,deps2) := node2;
  cmp := listLength(deps1) >= listLength(deps2);
end compareNumberOfDependencies;

protected function compareDependencyNode
  input tuple<String,list<String>> node1;
  input tuple<String,list<String>> node2;
  output Boolean cmp;
protected
  String s1,s2;
algorithm
  (s1,_) := node1;
  (s2,_) := node2;
  cmp := Util.strcmpBool(s1,s2);
end compareDependencyNode;

protected function dependencyString
  input tuple<String,list<String>> deps;
  output String str;
protected
  list<String> strs;
algorithm
  (str,strs) := deps;
  str := str + " (" + intString(listLength(strs)) + "): " + stringDelimitList(strs, ",");
end dependencyString;

protected function transitiveDependencyString
  input tuple<String,list<String>> deps;
  output String str;
protected
  list<String> strs;
algorithm
  (str,strs) := deps;
  str := intString(listLength(strs)) + ": ("+str+") " + stringDelimitList(strs, ",");
end transitiveDependencyString;

protected function containsPublicInterface
  input SCode.Element elt;
  output Boolean b;
algorithm
  b := match elt
    local
      list<SCode.Element> elts;
      String name;
    case SCode.CLASS(restriction=SCode.R_PACKAGE(), encapsulatedPrefix=SCode.ENCAPSULATED(), classDef=SCode.PARTS(elementLst=elts))
      then List.exist(elts, containsPublicInterface2);
    else
      equation
        name = SCodeUtil.elementName(elt);
        name = "CevalScript.containsPublicInterface failed: " + name;
        Error.addMessage(Error.INTERNAL_ERROR, {name});
      then fail();
  end match;
end containsPublicInterface;

protected function containsPublicInterface2
  "If the package contains a public type or constant, we depend on this package also through other modules"
  input SCode.Element elt;
  output Boolean b;
algorithm
  b := match elt
    local
      String name;
    case SCode.IMPORT() then false;
    case SCode.EXTENDS() then false;
    case SCode.CLASS(restriction=SCode.R_FUNCTION(_)) then false;
    case SCode.COMPONENT(prefixes=SCode.PREFIXES(visibility=SCode.PUBLIC()))
      equation
        // print("public component " + name + ": ");
      then true;
    case SCode.CLASS(prefixes=SCode.PREFIXES(visibility=SCode.PUBLIC()))
      equation
        // print("public class " + name + ": ");
      then true;
    else false;
  end match;
end containsPublicInterface2;

protected function containsImport
  input SCode.Element elt;
  input SCode.Visibility visibility;
  output Boolean b;
algorithm
  b := match (elt,visibility)
    local
      list<SCode.Element> elts;
      String name;
    case (SCode.CLASS(restriction=SCode.R_PACKAGE(), encapsulatedPrefix=SCode.ENCAPSULATED(), classDef=SCode.PARTS(elementLst=elts)),_)
      then List.exist1(elts, containsImport2, visibility);
    else
      equation
        name = SCodeUtil.elementName(elt);
        name = "CevalScript.containsPublicInterface failed: " + name;
        Error.addMessage(Error.INTERNAL_ERROR, {name});
      then fail();
  end match;
end containsImport;

protected function containsImport2
  "If the package contains a public type or constant, we depend on this package also through other modules"
  input SCode.Element elt;
  input SCode.Visibility visibility;
  output Boolean b;
algorithm
  b := match (elt,visibility)
    local
      String name;
    case (SCode.IMPORT(visibility=SCode.PUBLIC()),SCode.PUBLIC()) then true;
    case (SCode.IMPORT(visibility=SCode.PROTECTED()),SCode.PROTECTED()) then true;
    else false;
  end match;
end containsImport2;

protected function printInterfaceString
  input SCode.Element elt;
protected
  String str;
algorithm
  SCode.CLASS(name=str) := elt;
  print(str + ": " + boolString(containsPublicInterface(elt)) + "\n");
end printInterfaceString;

protected function writeModuleDepends
  input SCode.Element cl;
  input String prefix;
  input String suffix;
  input list<tuple<String,list<String>>> deps;
  output String str;
algorithm
  str := matchcontinue (cl,prefix,suffix,deps)
    local
      String name,fileName,tmp1;
      list<String> allDepends,protectedDepends,tmp2;
      list<SCode.Element> elts;
      SourceInfo info;
    case (SCode.CLASS(name=name, classDef=SCode.PARTS(elementLst=elts), info = SOURCEINFO()),_,_,_)
      equation
        protectedDepends = List.map(List.select(elts,SCodeUtil.elementIsProtectedImport),importDepenency);
        protectedDepends = List.select(protectedDepends, isNotBuiltinImport);
        _::allDepends = Graph.allReachableNodes((name::protectedDepends,{}),deps,stringEq);
        allDepends = List.map1r(allDepends, stringAppend, prefix);
        allDepends = List.map1(allDepends, stringAppend, ".interface.mo");
        str = prefix + name + suffix + ": $(RELPATH_" + name + ") " + stringDelimitList(allDepends," ");
      then str;
    case (SCode.CLASS(name=name, classDef=SCode.PARTS(elementLst=elts), info=info),_,_,_)
      algorithm
        protectedDepends := List.map(List.select(elts,SCodeUtil.elementIsProtectedImport),importDepenency);
        protectedDepends := List.select(protectedDepends, isNotBuiltinImport);
        allDepends := list(Util.tuple21(e) for e in deps);
        for d in protectedDepends loop
          if not listMember(d, allDepends) then
            Error.addSourceMessage(Error.GENERATE_SEPARATE_CODE_DEPENDENCIES_FAILED_UNKNOWN_PACKAGE, {name,name,d}, info);
            fail();
          end if;
        end for;
        for dep in deps loop
          (tmp1,tmp2) := dep;
          for d in tmp2 loop
            if not listMember(d, allDepends) then
              Error.addSourceMessage(Error.GENERATE_SEPARATE_CODE_DEPENDENCIES_FAILED_UNKNOWN_PACKAGE, {name,tmp1,d}, info);
              fail();
            end if;
          end for;
        end for;
      then fail();
    case (SCode.CLASS(name=name,info=info),_,_,_)
      equation
        Error.addSourceMessage(Error.GENERATE_SEPARATE_CODE_DEPENDENCIES_FAILED, {name}, info);
      then fail();
  end matchcontinue;
end writeModuleDepends;

protected function isNotBuiltinImport
  input String module;
  output Boolean b = module <> "MetaModelica";
end isNotBuiltinImport;

protected function getTypeNameIdent
  input Values.Value val;
  output String str;
algorithm
  Values.CODE(Absyn.C_TYPENAME(Absyn.IDENT(str))) := val;
end getTypeNameIdent;

protected function getChangedClass
  input SCode.Element elt;
  input String suffix;
  output String name;
algorithm
  name := matchcontinue (elt,suffix)
    local
      String fileName;
    case (SCode.CLASS(name=name,info=SOURCEINFO()),_)
      equation
        false = System.regularFileExists(name + suffix);
      then name;
    case (SCode.CLASS(name=name,info=SOURCEINFO(fileName=fileName)),_)
      equation
        true = System.fileIsNewerThan(fileName, name + suffix);
      then name;
  end matchcontinue;
end getChangedClass;

protected function isChanged
  input tuple<String,list<String>> node;
  input HashSetString.HashSet hs;
  output Boolean b;
protected
  String str;
  list<String> strs;
algorithm
  (str,strs) := node;
  b := List.exist1(str::strs,BaseHashSet.has,hs);
  // print(str + ": " +  boolString(b) + "\n");
end isChanged;

protected function reloadClass
  input String filename;
  input String encoding;
protected
  Absyn.Program p,newp;
algorithm
  newp := Parser.parse(filename,encoding); /* Don't use the classloader since that can pull in entire directory structures. We only want to reload one single file. */
  newp := InteractiveUtil.updateProgram(newp, SymbolTable.getAbsyn());
  SymbolTable.setAbsyn(newp);
end reloadClass;

public function getFullPathFromUri
  input Absyn.Program program;
  input String uri;
  input Boolean printError;
  output String path;
protected
  String str1,str2,str3;
algorithm
  (str1,str2,str3) := System.uriToClassAndPath(uri);
  path := getBasePathFromUri(str1,str2,program,Settings.getModelicaPath(Testsuite.isRunning()),printError) + str3;
end getFullPathFromUri;

protected function getBasePathFromUri "Handle modelica:// URIs"
  input String scheme;
  input String iname;
  input Absyn.Program program;
  input String modelicaPath;
  input Boolean printError;
  output String basePath;
algorithm
  basePath := matchcontinue (scheme,iname,program,modelicaPath,printError)
    local
      Boolean isDir;
      list<String> mps,names;
      String gd,mp,bp,str,name,version,fileName;
    case ("modelica://",name,_,_,_)
      equation
        (name::names) = System.strtok(name,".");
        Absyn.CLASS(info=SOURCEINFO(fileName=fileName)) = InteractiveUtil.getPathedClassInProgram(Absyn.IDENT(name),program);
        mp = System.dirname(fileName);
        bp = findModelicaPath2(mp,names,"",true);
      then bp;
    case ("modelica://",name,_,mp,_)
      equation
        (name::names) = System.strtok(name,".");
        failure(_ = InteractiveUtil.getPathedClassInProgram(Absyn.IDENT(name),program));
        gd = Autoconf.groupDelimiter;
        mps = System.strtok(mp, gd);
        (mp,name,isDir) = System.getLoadModelPath(name, {"default"}, mps);
        mp = if isDir then mp + name else mp;
        bp = findModelicaPath2(mp,names,"",true);
      then bp;
    case ("file://",_,_,_,_) then "";
    case ("modelica://",name,_,mp,true)
      equation
        name::_ = System.strtok(name,".");
        str = "Could not resolve modelica://" + name + " with MODELICAPATH: " + mp;
        Error.addMessage(Error.COMPILER_ERROR,{str});
      then fail();
  end matchcontinue;
end getBasePathFromUri;

protected function findModelicaPath "Handle modelica:// URIs"
  input list<String> imps;
  input list<String> names;
  input String version;
  output String basePath;
algorithm
  basePath := matchcontinue (imps,names,version)
    local
      String mp;
      list<String> mps;

    case (mp::_,_,_)
      then findModelicaPath2(mp,names,version,false);
    case (_::mps,_,_)
      then findModelicaPath(mps,names,version);
  end matchcontinue;
end findModelicaPath;

protected function findModelicaPath2 "Handle modelica:// URIs"
  input String mp;
  input list<String> inames;
  input String version;
  input Boolean b;
  output String basePath;
algorithm
  basePath := matchcontinue (mp,inames,version,b)
    local
      list<String> names;
      String name,file;

    case (_,name::names,_,_)
      equation
        false = stringEq(version,"");
        file = mp + "/" + name + " " + version;
        true = System.directoryExists(file);
        // print("Found file 1: " + file + "\n");
      then findModelicaPath2(file,names,"",true);
    case (_,name::_,_,_)
      equation
        false = stringEq(version,"");
        file = mp + "/" + name + " " + version + ".mo";
        true = System.regularFileExists(file);
        // print("Found file 2: " + file + "\n");
      then mp;

    case (_,name::names,_,_)
      equation
        file = mp + "/" + name;
        true = System.directoryExists(file);
        // print("Found file 3: " + file + "\n");
      then findModelicaPath2(file,names,"",true);
    case (_,name::_,_,_)
      equation
        file = mp + "/" + name + ".mo";
        true = System.regularFileExists(file);
        // print("Found file 4: " + file + "\n");
      then mp;

      // This class is part of the current package.mo, or whatever...
    case (_,_,_,true)
      equation
        // print("Did not find file 5: " + mp + " - " + name + "\n");
      then mp;
  end matchcontinue;
end findModelicaPath2;

protected function unZipEncryptedPackageAndCheckFile
  input String inWorkdir;
  input String filename;
  input Boolean skipUnzip;
  output Boolean success;
  output String outFilename;
protected
  String workdir, s1, s2, s3, filename_1, filename1, filename2, filename3, filename4, str, str1, str2, str3, str4, cmd, cmdPrefix;
  Boolean isWindows = Autoconf.os == "Windows_NT";
algorithm
  success := false;
  outFilename := "";
  if (System.regularFileExists(filename)) then
    if (StringUtil.endsWith(filename, ".mol")) then
      workdir := if System.directoryExists(inWorkdir) then inWorkdir else System.pwd();
      // use ripunzip (https://github.com/google/ripunzip) on Windows as is twice as fast
      // TODO on Linux we should check if it is in the path
      cmdPrefix := if isWindows then "ripunzip.exe -q unzip-file -d " else "unzip -q -o -d ";
      cmd := cmdPrefix + "\"" + workdir + "\" \"" + filename + "\"";
      if (skipUnzip or 0 == System.systemCall(cmd)) then
        s1 := System.basename(filename);
        s2 := Util.removeLast4Char(s1);
        s3 := listGet(Util.stringSplitAtChar(s2," "),1);
        // possible .moc files to look for
        filename1 := workdir + "/" + s2 + "/package.moc";
        filename2 := workdir + "/" + s2 + "/" + s2 + ".moc";
        filename3 := workdir + "/" + s3 + "/package.moc";
        filename4 := workdir + "/" + s3 + "/" + s3 + ".moc";
        if System.regularFileExists(filename1) then
          filename_1 := filename1;
        elseif System.regularFileExists(filename2) then
          filename_1 := filename2;
        elseif System.regularFileExists(filename3) then
          filename_1 := filename3;
        else
          filename_1 := filename4;
        end if;
        // possible .mo files to look for
        str1 := workdir + "/" + s2 + "/package.mo";
        str2 := workdir + "/" + s2 + "/" + s2 + ".mo";
        str3 := workdir + "/" + s3 + "/package.mo";
        str4 := workdir + "/" + s3 + "/" + s3 + ".mo";
        if System.regularFileExists(str1) then
          str := str1;
        elseif System.regularFileExists(str2) then
          str := str2;
        elseif System.regularFileExists(str3) then
          str := str3;
        else
          str := str4;
        end if;
        // check if .mol contains .moc or .mo files
        filename_1 := if System.regularFileExists(filename_1) then filename_1 else str;
        if (System.regularFileExists(filename_1)) then
          success := true;
          outFilename := filename_1;
        else
          Error.addMessage(Error.PACKAGE_FILE_NOT_FOUND_ERROR, {filename1, filename2, filename3, filename4, str1, str2, str3, str4});
        end if;
      else
        Error.addMessage(Error.UNABLE_TO_UNZIP_FILE, {filename});
      end if;
    else
      Error.addMessage(Error.EXPECTED_ENCRYPTED_PACKAGE, {filename});
    end if;
  else
    Error.addMessage(Error.FILE_NOT_FOUND_ERROR, {filename});
  end if;
end unZipEncryptedPackageAndCheckFile;

function listClass
  input list<Values.Value> args;
  output Values.Value res;
protected
  String str, name;
  Absyn.Program p;
  SCode.Program scodeP;
  Absyn.Class absynClass;
  SCode.Element cl;
  Absyn.Path path, className;
  Boolean interface_only, short_only;
  constant SCodeDump.SCodeDumpOptions dumpOpt =
    SCodeDump.OPTIONS(true,false,true,true,true,true,true,true,true);
algorithm
  str := matchcontinue args
    // handle encryption
    case _
      algorithm
        // if AST contains encrypted class show nothing
        p := SymbolTable.getAbsyn();
        true := Interactive.astContainsEncryptedClass(p);
        Error.addMessage(Error.ACCESS_ENCRYPTED_PROTECTED_CONTENTS, {});
      then
        "";

    case {Values.CODE(Absyn.C_TYPENAME(Absyn.IDENT("AllLoadedClasses"))),
          Values.BOOL(false), Values.BOOL(false), Values.ENUM_LITERAL(name=path)}
      then
        match AbsynUtil.pathLastIdent(path)
          case "Absyn" then Dump.unparseStr(SymbolTable.getAbsyn(), false);
          case "SCode" then SCodeDump.programStr(SymbolTable.getSCode());
          case "MetaModelicaInterface" then SCodeDump.programStr(SymbolTable.getSCode(), dumpOpt);
          case "Internal" then System.anyStringCode(SymbolTable.getAbsyn());
          else "";
        end match;

    case {Values.CODE(Absyn.C_TYPENAME(className)), Values.BOOL(interface_only),
          Values.BOOL(short_only), Values.ENUM_LITERAL(name=path)}
      algorithm
        false := valueEq(Absyn.IDENT("AllLoadedClasses"),className);
        p := SymbolTable.getAbsyn();
        scodeP := SymbolTable.getSCode();
        absynClass := InteractiveUtil.getPathedClassInProgram(className, p);
        absynClass := if interface_only then AbsynUtil.getFunctionInterface(absynClass) else absynClass;
        absynClass := if short_only then AbsynUtil.getShortClass(absynClass) else absynClass;
        p := Absyn.PROGRAM({absynClass},Absyn.TOP());
        cl := FBuiltin.getElementWithPathCheckBuiltin(scodeP, className);
      then
        match AbsynUtil.pathLastIdent(path)
          case "Absyn" then Dump.unparseStr(p, false);
          case "SCode" then SCodeDump.unparseElementStr(cl);
          case "MetaModelicaInterface" then SCodeDump.unparseElementStr(cl, dumpOpt);
          case "Internal" then System.anyStringCode(p);
          else "";
        end match;

    else "";
  end matchcontinue;

  res := Values.STRING(str);
end listClass;

function listFile
  input list<Values.Value> args;
  output Values.Value res;
protected
  String str;
  Absyn.Path path, className;
  Boolean nested;
  Integer access;
  Absyn.Class absynClass;
  Absyn.Restriction restriction;
algorithm
  str := matchcontinue args
    case {Values.CODE(Absyn.C_TYPENAME(className)), Values.BOOL(nested)}
      algorithm
        path := match className
          case Absyn.FULLYQUALIFIED() then className.path;
          else className;
        end match;
        // handle encryption
        Values.ENUM_LITERAL(index=access) := Interactive.checkAccessAnnotationAndEncryption(path, SymbolTable.getAbsyn());
        (absynClass as Absyn.CLASS(restriction=restriction, info=SOURCEINFO(fileName=str))) := InteractiveUtil.getPathedClassInProgram(className, SymbolTable.getAbsyn());
        absynClass := if nested then absynClass else AbsynUtil.filterNestedClasses(absynClass);
        /* If the class has Access.packageText annotation or higher
         * If the class has Access.nonPackageText annotation or higher and class is not a package
         */
        if ((access >= 7) or ((access >= 5) and not AbsynUtil.isPackageRestriction(restriction))) then
          str := Dump.unparseStr(Absyn.PROGRAM({absynClass}, match path case Absyn.IDENT() then Absyn.TOP(); else Absyn.WITHIN(AbsynUtil.stripLast(path)); end match), options=Dump.DUMPOPTIONS(str));
        else
          Error.addMessage(Error.ACCESS_ENCRYPTED_PROTECTED_CONTENTS, {});
          str := "";
        end if;
      then
        str;

    else "";
  end matchcontinue;

  res := Values.STRING(str);
end listFile;

function getClassNames
  input list<Values.Value> args;
  output Values.Value res;
protected
  Absyn.Path path;
  String clsName;
  Boolean recursive, qualified, sort, builtin, protects, constants;
  Absyn.Program p;
  list<Absyn.Path> paths;
algorithm
  {Values.CODE(Absyn.C_TYPENAME(path)),
   Values.BOOL(recursive),
   Values.BOOL(qualified),
   Values.BOOL(sort),
   Values.BOOL(builtin),
   Values.BOOL(protects),
   Values.BOOL(constants)} := args;

  p := SymbolTable.getAbsyn();

  if builtin then
    p := InteractiveUtil.updateProgram(p, FBuiltin.getInitialFunctions());
  end if;

  if AbsynUtil.pathEqual(path, Absyn.IDENT("AllLoadedClasses")) then
    if recursive then
      (_, paths) := InteractiveUtil.getClassNamesRecursive(NONE(), p, protects, constants, {});
      paths := listReverseInPlace(paths);
    else
      paths := Interactive.getTopClassnames(p);
    end if;
  else
    if recursive then
      (_, paths) := InteractiveUtil.getClassNamesRecursive(SOME(path), p, protects, constants, {});
      paths := listReverseInPlace(paths);
    else
      paths := Interactive.getClassnamesInPath(path, p, protects, constants);

      if qualified then
        paths := list(AbsynUtil.joinPaths(path, p) for p in paths);
      end if;
    end if;
  end if;

  if sort then
    paths := List.sort(paths, AbsynUtil.pathGe);
  end if;

  res := ValuesUtil.makeArray(list(ValuesUtil.makeCodeTypeName(p) for p in paths));
end getClassNames;

function checkSettings
  output Values.Value res;
protected
  list<String> vars;
  String omhome, omlib, omcpath, systemPath, omdev, os, touch_file, usercflags;
  String workdir, uname, senddata, gcc, gccVersion, confcmd;
  Boolean omcfound, touch_res, rm_res, have_corba, gcc_res;
  list<Values.Value> vals;
algorithm
  vars := {"OPENMODELICAHOME",
           "OPENMODELICALIBRARY",
           "OMC_PATH",
           "SYSTEM_PATH",
           "OMDEV_PATH",
           "OMC_FOUND",
           "MODELICAUSERCFLAGS",
           "WORKING_DIRECTORY",
           "CREATE_FILE_WORKS",
           "REMOVE_FILE_WORKS",
           "OS",
           "SYSTEM_INFO",
           "RTLIBS",
           "C_COMPILER",
           "C_COMPILER_VERSION",
           "C_COMPILER_RESPONDING",
           "HAVE_CORBA",
           "CONFIGURE_CMDLINE"};
  omhome := Settings.getInstallationDirectoryPath();
  omlib := Settings.getModelicaPath(Testsuite.isRunning());
  omcpath := omhome + "/bin/omc" + Autoconf.exeExt;
  systemPath := Util.makeValueOrDefault(System.readEnv,"PATH","");
  omdev := Util.makeValueOrDefault(System.readEnv,"OMDEV","");
  omcfound := System.regularFileExists(omcpath);
  os := Autoconf.os;
  touch_file := "omc.checksettings.create_file_test";
  usercflags := Util.makeValueOrDefault(System.readEnv,"MODELICAUSERCFLAGS","");
  workdir := System.pwd();
  touch_res := 0 == System.systemCall("touch " + touch_file, "");
  System.systemCall("uname -a", touch_file);
  uname := System.readFile(touch_file);
  rm_res := 0 == System.systemCall("rm " + touch_file, "");
  // _ = System.platform();
  senddata := Autoconf.ldflags_runtime;
  gcc := System.getCCompiler();
  have_corba := Corba.haveCorba();
  System.systemCall("rm -f " + touch_file, "");
  gcc_res := 0 == System.systemCall(gcc + " --version", touch_file);
  gccVersion := System.readFile(touch_file);
  System.systemCall("rm -f " + touch_file, "");
  confcmd := Autoconf.configureCommandLine;
  vals := {Values.STRING(omhome),
           Values.STRING(omlib),
           Values.STRING(omcpath),
           Values.STRING(systemPath),
           Values.STRING(omdev),
           Values.BOOL(omcfound),
           Values.STRING(usercflags),
           Values.STRING(workdir),
           Values.BOOL(touch_res),
           Values.BOOL(rm_res),
           Values.STRING(os),
           Values.STRING(uname),
           Values.STRING(senddata),
           Values.STRING(gcc),
           Values.STRING(gccVersion),
           Values.BOOL(gcc_res),
           Values.BOOL(have_corba),
           Values.STRING(confcmd)};

  res := Values.RECORD(Absyn.IDENT("OpenModelica.Scripting.CheckSettingsResult"), vals, vars, -1);
end checkSettings;

function generateSeparateCodeDependenciesMakefile
  input list<Values.Value> args;
  output Values.Value res;
protected
  SCode.Program sp;
  list<String> names, strs;
  list<tuple<String,list<String>>> deps;
  String filename, prefix, suffix;
algorithm
  try
    {Values.STRING(filename),Values.STRING(prefix),Values.STRING(suffix)} := args;
    sp := SymbolTable.getSCode();
    names := List.filterMap(sp,SCodeUtil.getElementName);
    deps := Graph.buildGraph(names,buildDependencyGraphPublicImports,sp);
    strs := List.map3(sp,writeModuleDepends,prefix,suffix,deps);
    System.writeFile(filename,stringDelimitList(strs,"\n"));
    res := Values.BOOL(true);
  else
    res := Values.BOOL(false);
  end try;
end generateSeparateCodeDependenciesMakefile;

function generateSeparateCodeDependencies
  input list<Values.Value> args;
  output Values.Value res;
protected
  String suffix;
  SCode.Program sp;
  list<String> names, namesPublic, namesChanged, fileNames;
  list<tuple<String,list<String>>> deps, depstransitive, depstransposed,
                                   depstransposedtransitive, depsmerged, depschanged;
  HashSetString.HashSet hashSetString;
algorithm
  try
    {Values.STRING(suffix)} := args;
    sp := SymbolTable.getSCode();
    names := List.filterMap(sp,SCodeUtil.getElementName);

    deps := Graph.buildGraph(names,buildDependencyGraph,sp);
    namesPublic := List.map(List.select(sp, containsPublicInterface), SCodeUtil.getElementName);
    namesChanged := List.filterMap1(sp,getChangedClass,suffix);
    hashSetString := HashSetString.emptyHashSet();
    hashSetString := List.fold(namesChanged,BaseHashSet.add,hashSetString);
    // print("namesChanged: " + stringDelimitList(namesChanged, ",") + "\n");

    depstransposed := Graph.transposeGraph(Graph.emptyGraph(names),deps,stringEq);
    depstransposedtransitive := Graph.buildGraph(namesPublic,buildTransitiveDependencyGraph,depstransposed);
    // depstransposedtransitive = List.sort(depstransposed, compareNumberOfDependencies);

    depstransitive := Graph.transposeGraph(Graph.emptyGraph(names),depstransposedtransitive,stringEq);
    depstransitive := List.sort(depstransitive, compareNumberOfDependencies);

    depsmerged := Graph.merge(deps,depstransitive,stringEq,compareDependencyNode);
    // depsmerged = List.sort(depsmerged, compareNumberOfDependencies);

    /*
     print("Total number of modules: " + intString(listLength(depsmerged)) + "\n");
     str = stringDelimitList(List.map(depsmerged, transitiveDependencyString), "\n");
     print(str + "\n");
    */

    depschanged := List.select1(depsmerged,isChanged,hashSetString);
    names := List.map(depschanged, Util.tuple21);
    // print("Files to recompile (" + intString(listLength(depschanged)) + "): " + stringDelimitList(names, ",") + "\n");
    fileNames := List.map1(names, stringAppend, suffix);
    _ := List.map(fileNames, System.removeFile);
    res := ValuesUtil.makeArray(List.map(names,ValuesUtil.makeString));
  else
    res := Values.META_FAIL();
  end try;
end generateSeparateCodeDependencies;

function generateSeparateCode
  input list<Values.Value> args;
  input FCore.Cache cache;
  input FCore.Graph env;
  output Values.Value res;
  output FCore.Cache outCache;
protected
  Values.Value v;
  Boolean b;
  Absyn.Program p;
  SCode.Program sp;
  String name;
  SCode.Element cl;
algorithm
  res := matchcontinue args
    case {v, Values.BOOL(b)}
      algorithm
        p := SymbolTable.getAbsyn();
        sp := SymbolTable.getSCode();
        name := getTypeNameIdent(v);
        setGlobalRoot(Global.instOnlyForcedFunctions,SOME(true));
        cl := List.getMemberOnTrue(name, sp, SCodeUtil.isClassNamed);
        outCache := generateFunctions(cache,env,p,sp,{cl},b);
        setGlobalRoot(Global.instOnlyForcedFunctions,NONE());
      then
        Values.BOOL(true);

    case {v, Values.BOOL(_)}
      algorithm
        sp := SymbolTable.getSCode();
        name := getTypeNameIdent(v);
        false := List.isMemberOnTrue(name, sp, SCodeUtil.isClassNamed);
        Error.addMessage(Error.LOOKUP_ERROR, {name,"<TOP>"});
      then
        fail();

    else
      algorithm
        setGlobalRoot(Global.instOnlyForcedFunctions,NONE());
      then
        Values.BOOL(false);

  end matchcontinue;
end generateSeparateCode;


public function getImportedNames
  input Absyn.Class inClass;
  output list<Values.Value> outPublicImports;
  output list<Values.Value> outProtectedImports;
protected
  String ident;
  list<Absyn.Import> pub_imports_list , pro_imports_list;
algorithm
  (pub_imports_list , pro_imports_list) := getImportList(inClass);

  outPublicImports := {};
  for imp in pub_imports_list loop
     ident := AbsynUtil.pathFirstIdent(AbsynUtil.importPath(imp));
     if ident <> "MetaModelica" then
       outPublicImports := Values.STRING(ident)::outPublicImports;
     end if;
  end for;

  outProtectedImports := {};
  for imp in pro_imports_list loop
     ident := AbsynUtil.pathFirstIdent(AbsynUtil.importPath(imp));
     if ident <> "MetaModelica" then
       outProtectedImports := Values.STRING(ident)::outProtectedImports;
     end if;
  end for;
end getImportedNames;

public function getImportList
"Counts the number of Import sections in a class."
  input Absyn.Class inClass;
  input output list<Absyn.Import> pub_imports_list = {};
  input output list<Absyn.Import> pro_imports_list = {};
algorithm
  () := match (inClass)
    local
      list<Absyn.ClassPart> parts;

    case Absyn.CLASS(body = Absyn.PARTS(classParts = parts)) algorithm
      for part in parts loop
        (pub_imports_list, pro_imports_list) := getImportsInClassPart(part, pub_imports_list, pro_imports_list);
      end for;
    then ();

    // check also the case model extends X end X;
    case Absyn.CLASS(body = Absyn.CLASS_EXTENDS(parts = parts)) algorithm
      for part in parts loop
        (pub_imports_list, pro_imports_list) := getImportsInClassPart(part, pub_imports_list, pro_imports_list);
      end for;
    then ();

    case Absyn.CLASS(body = Absyn.DERIVED()) then ();
  end match;
end getImportList;

protected function getImportsInClassPart
  input Absyn.ClassPart inAbsynClassPart;
  input output list<Absyn.Import> pub_imports_list;
  input output list<Absyn.Import> pro_imports_list;
algorithm
  () := matchcontinue (inAbsynClassPart)
    local
      list<Absyn.ElementItem> els;
    case Absyn.PUBLIC(contents = els) algorithm
      for elem in els loop
        pub_imports_list := getImportsInElementItem(elem, pub_imports_list);
      end for;
    then ();

    case Absyn.PROTECTED(contents = els) algorithm
      for elem in els loop
        pro_imports_list := getImportsInElementItem(elem, pro_imports_list);
      end for;
    then ();

    else ();

  end matchcontinue;
end getImportsInClassPart;

protected function getImportsInElementItem
"Helper function to getImportCount"
  input Absyn.ElementItem inAbsynElementItem;
  input output list<Absyn.Import> imports_list;
algorithm
  () := matchcontinue inAbsynElementItem
    local
      Absyn.Import import_;
      Absyn.Class class_;

    case Absyn.ELEMENTITEM(element = Absyn.ELEMENT(specification = Absyn.IMPORT(import_ = import_)))
      algorithm
        imports_list := import_::imports_list;
      then ();

    case Absyn.ELEMENTITEM(element = Absyn.ELEMENT(specification = Absyn.CLASSDEF(class_ = class_)))
      algorithm
        // imports_list := getImportList(class_, get_protected, imports_list);
      then ();

    else ();
  end matchcontinue;
end getImportsInElementItem;

protected function getMMfileTotalDependencies
  input String in_package_name;
  input String public_imports_dir;
  output list<String> total_pub_imports = {};
protected
  Absyn.Class package_class;
  list<Absyn.Import> pub_imports_list , pro_imports_list;
  String imp_ident;
algorithm
  package_class := InteractiveUtil.getPathedClassInProgram(Absyn.IDENT(in_package_name), SymbolTable.getAbsyn());

  (pub_imports_list , pro_imports_list) := getImportList(package_class);

  for imp in pub_imports_list loop
   imp_ident := AbsynUtil.pathFirstIdent(AbsynUtil.importPath(imp));
   if imp_ident <> "MetaModelica" then
     total_pub_imports := getMMfilePublicDependencies(imp_ident, public_imports_dir, total_pub_imports);
   end if;
  end for;

  for imp in pro_imports_list loop
   imp_ident := AbsynUtil.pathFirstIdent(AbsynUtil.importPath(imp));
   if imp_ident <> "MetaModelica" then
     total_pub_imports := getMMfilePublicDependencies(imp_ident, public_imports_dir, total_pub_imports);
   end if;
  end for;

end getMMfileTotalDependencies;

protected function getMMfilePublicDependencies
  input String in_package_name;
  input String public_imports_dir;
  input output list<String> packages;
protected
  String dep_public_imports_file, pub_imports_total;
algorithm
  if listMember(in_package_name, packages) then
    return;
  end if;

  packages := in_package_name::packages;

  dep_public_imports_file := public_imports_dir + "/" + in_package_name + ".public.imports";
  pub_imports_total := System.readFile(dep_public_imports_file);

  for pub_imp in System.strtok(pub_imports_total, ";") loop
    packages := getMMfilePublicDependencies(pub_imp, public_imports_dir, packages);
  end for;

end getMMfilePublicDependencies;

annotation(__OpenModelica_Interface="backend");
end CevalScript;
